Merge "Do not set persist.heapprofd.enable for CTS."
diff --git a/.clang-tidy b/.clang-tidy
index b7aebb1..5f168ce 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,4 +1,6 @@
-Checks: android-cloexec-*,bugprone-*,google-explicit-constructor
+Checks: android-cloexec-*,bugprone-*,google-explicit-constructor,android-comparison-in-temp-failure-retry
 CheckOptions:
   - key:             bugprone-assert-side-effect.AssertMacros
     value:           'PERFETTO_DCHECK'
+  - key:             android-comparison-in-temp-failure-retry.RetryMacros
+    value:           'PERFETTO_EINTR,TEMP_FAILURE_RETRY'
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..b8c67a0
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,6 @@
+Please do not submit a Pull Request via GitHub.
+Our project makes use of Gerrit for patch submission and review.
+
+For more details please see
+https://perfetto.dev/docs/contributing/getting-started
+
diff --git a/Android.bp b/Android.bp
index 66ca9a7..0da74e3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,67 +14,6 @@
 //
 // This file is automatically generated by tools/gen_android_bp. Do not edit.
 
-// GN: //src/trace_processor/metrics:gen_merged_sql_metrics
-genrule {
-  name: "gen_merged_sql_metrics",
-  srcs: [
-    "src/trace_processor/metrics/android/android_batt.sql",
-    "src/trace_processor/metrics/android/android_cpu.sql",
-    "src/trace_processor/metrics/android/android_cpu_agg.sql",
-    "src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql",
-    "src/trace_processor/metrics/android/android_hwui_metric.sql",
-    "src/trace_processor/metrics/android/android_ion.sql",
-    "src/trace_processor/metrics/android/android_lmk.sql",
-    "src/trace_processor/metrics/android/android_lmk_reason.sql",
-    "src/trace_processor/metrics/android/android_mem.sql",
-    "src/trace_processor/metrics/android/android_mem_unagg.sql",
-    "src/trace_processor/metrics/android/android_package_list.sql",
-    "src/trace_processor/metrics/android/android_powrails.sql",
-    "src/trace_processor/metrics/android/android_proxy_power.sql",
-    "src/trace_processor/metrics/android/android_startup.sql",
-    "src/trace_processor/metrics/android/android_startup_launches.sql",
-    "src/trace_processor/metrics/android/android_surfaceflinger.sql",
-    "src/trace_processor/metrics/android/android_task_names.sql",
-    "src/trace_processor/metrics/android/android_task_state.sql",
-    "src/trace_processor/metrics/android/android_thread_time_in_state.sql",
-    "src/trace_processor/metrics/android/counter_span_view.sql",
-    "src/trace_processor/metrics/android/cpu_info.sql",
-    "src/trace_processor/metrics/android/display_metrics.sql",
-    "src/trace_processor/metrics/android/frame_missed.sql",
-    "src/trace_processor/metrics/android/heap_profile_callsites.sql",
-    "src/trace_processor/metrics/android/hsc_startups.sql",
-    "src/trace_processor/metrics/android/java_heap_histogram.sql",
-    "src/trace_processor/metrics/android/java_heap_stats.sql",
-    "src/trace_processor/metrics/android/mem_stats_priority_breakdown.sql",
-    "src/trace_processor/metrics/android/power_profile_data.sql",
-    "src/trace_processor/metrics/android/process_counter_span_view.sql",
-    "src/trace_processor/metrics/android/process_mem.sql",
-    "src/trace_processor/metrics/android/process_metadata.sql",
-    "src/trace_processor/metrics/android/process_oom_score.sql",
-    "src/trace_processor/metrics/android/process_unagg_mem_view.sql",
-    "src/trace_processor/metrics/android/span_view_stats.sql",
-    "src/trace_processor/metrics/android/unmapped_java_symbols.sql",
-    "src/trace_processor/metrics/android/unsymbolized_frames.sql",
-    "src/trace_processor/metrics/chrome/chrome_processes.sql",
-    "src/trace_processor/metrics/chrome/console_error_metric.sql",
-    "src/trace_processor/metrics/chrome/scroll_flow_event.sql",
-    "src/trace_processor/metrics/chrome/scroll_flow_event_queuing_delay.sql",
-    "src/trace_processor/metrics/chrome/scroll_jank.sql",
-    "src/trace_processor/metrics/chrome/scroll_jank_cause.sql",
-    "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_task.sql",
-    "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_touch_move.sql",
-    "src/trace_processor/metrics/trace_metadata.sql",
-    "src/trace_processor/metrics/webview/webview_power_usage.sql",
-  ],
-  cmd: "$(location tools/gen_merged_sql_metrics.py) --cpp_out=$(out) $(in)",
-  out: [
-    "src/trace_processor/metrics/sql_metrics.h",
-  ],
-  tool_files: [
-    "tools/gen_merged_sql_metrics.py",
-  ],
-}
-
 // GN: //src/profiling/memory:heapprofd
 cc_binary {
   name: "heapprofd",
@@ -100,6 +39,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -138,12 +79,14 @@
     ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_interning_output",
     ":perfetto_src_profiling_common_proc_utils",
+    ":perfetto_src_profiling_common_profiler_guardrails",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_memory_daemon",
     ":perfetto_src_profiling_memory_ring_buffer",
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_wire_protocol",
     ":perfetto_src_protozero_protozero",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_tracing_common",
     ":perfetto_src_tracing_core_core",
     ":perfetto_src_tracing_ipc_common",
@@ -171,6 +114,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -201,6 +146,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -209,6 +155,24 @@
     "-DGOOGLE_PROTOBUF_NO_RTTI",
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ],
+  header_libs: [
+    "bionic_libc_platform_headers",
+  ],
+}
+
+// GN: //src/profiling/memory:heapprofd_api_noop
+cc_library_shared {
+  name: "heapprofd_api_noop",
+  srcs: [
+    "src/profiling/memory/client_api_noop.cc",
+  ],
+  export_include_dirs: [
+    "include",
+    "include/perfetto/base/build_configs/android_tree",
+  ],
+  defaults: [
+    "perfetto_defaults",
+  ],
 }
 
 // GN: //src/profiling/memory:heapprofd_client
@@ -219,6 +183,7 @@
     ":perfetto_include_perfetto_ext_base_base",
     ":perfetto_src_base_base",
     ":perfetto_src_profiling_memory_malloc_hooks",
+    ":perfetto_src_profiling_memory_wrap_allocators",
   ],
   shared_libs: [
     "heapprofd_client_api",
@@ -230,6 +195,9 @@
     "include",
     "include/perfetto/base/build_configs/android_tree",
   ],
+  generated_headers: [
+    "perfetto_src_base_version_gen_h",
+  ],
   defaults: [
     "perfetto_defaults",
   ],
@@ -255,11 +223,11 @@
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_profiling_common_proc_utils",
     ":perfetto_src_profiling_memory_client",
-    ":perfetto_src_profiling_memory_client_ext",
+    ":perfetto_src_profiling_memory_client_api",
     ":perfetto_src_profiling_memory_ring_buffer",
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_wire_protocol",
-    "src/profiling/memory/client_ext_android.cc",
+    "src/profiling/memory/client_api_android.cc",
   ],
   shared_libs: [
     "libbase",
@@ -273,6 +241,9 @@
     "include",
     "include/perfetto/base/build_configs/android_tree",
   ],
+  generated_headers: [
+    "perfetto_src_base_version_gen_h",
+  ],
   defaults: [
     "perfetto_defaults",
   ],
@@ -315,6 +286,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -353,19 +326,21 @@
     ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_interning_output",
     ":perfetto_src_profiling_common_proc_utils",
+    ":perfetto_src_profiling_common_profiler_guardrails",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_memory_client",
-    ":perfetto_src_profiling_memory_client_ext",
+    ":perfetto_src_profiling_memory_client_api",
+    ":perfetto_src_profiling_memory_client_api_standalone",
     ":perfetto_src_profiling_memory_daemon",
     ":perfetto_src_profiling_memory_ring_buffer",
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_wire_protocol",
     ":perfetto_src_protozero_protozero",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_tracing_common",
     ":perfetto_src_tracing_core_core",
     ":perfetto_src_tracing_ipc_common",
     ":perfetto_src_tracing_ipc_producer_producer",
-    "src/profiling/memory/client_ext_standalone.cc",
   ],
   shared_libs: [
     "liblog",
@@ -394,6 +369,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -424,6 +401,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -433,6 +411,10 @@
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
     "-DPERFETTO_ANDROID_ASYNC_SAFE_LOG",
   ],
+  header_libs: [
+    "bionic_libc_platform_headers",
+  ],
+  stl: "libc++_static",
   version_script: "src/profiling/memory/heapprofd_client_api.map.txt",
 }
 
@@ -448,6 +430,9 @@
   static_libs: [
     "libprotoc",
   ],
+  generated_headers: [
+    "perfetto_src_base_version_gen_h",
+  ],
   defaults: [
     "perfetto_defaults",
   ],
@@ -484,6 +469,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -516,22 +503,24 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
     ":perfetto_src_ipc_host",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_kallsyms_kallsyms",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_traced_probes_android_log_android_log",
     ":perfetto_src_traced_probes_common_common",
     ":perfetto_src_traced_probes_data_source",
     ":perfetto_src_traced_probes_filesystem_filesystem",
-    ":perfetto_src_traced_probes_ftrace_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_format_parser",
     ":perfetto_src_traced_probes_ftrace_ftrace",
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes",
     ":perfetto_src_traced_probes_probes_src",
@@ -548,9 +537,6 @@
     ":perfetto_src_tracing_ipc_producer_producer",
     ":perfetto_src_tracing_ipc_service_service",
   ],
-  shared_libs: [
-    "liblog",
-  ],
   host_supported: true,
   export_include_dirs: [
     "include",
@@ -568,6 +554,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -598,6 +586,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -606,6 +595,13 @@
     "-DGOOGLE_PROTOBUF_NO_RTTI",
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ],
+  target: {
+    android: {
+      shared_libs: [
+        "liblog",
+      ],
+    },
+  },
 }
 
 // GN: //src/android_internal:libperfetto_android_internal
@@ -614,7 +610,7 @@
   srcs: [
     ":perfetto_src_android_internal_android_internal",
     ":perfetto_src_android_internal_headers",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_android_stats_perfetto_atoms",
   ],
   shared_libs: [
     "android.hardware.atrace@1.0",
@@ -672,6 +668,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -720,6 +718,9 @@
     ":perfetto_src_tracing_platform_posix",
     ":perfetto_src_tracing_system_backend",
   ],
+  shared_libs: [
+    "liblog",
+  ],
   export_include_dirs: [
     "include",
     "include/perfetto/base/build_configs/android_tree",
@@ -736,6 +737,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -766,6 +769,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   export_generated_headers: [
     "perfetto_protos_perfetto_common_cpp_gen_headers",
@@ -779,6 +783,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -809,6 +815,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -819,8 +826,8 @@
   ],
   apex_available: [
     "//apex_available:platform",
+    "com.android.art",
     "com.android.art.debug",
-    "com.android.art.release",
   ],
 }
 
@@ -849,6 +856,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -881,11 +890,12 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_android_stats",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
     ":perfetto_src_perfetto_cmd_perfetto_cmd",
     ":perfetto_src_perfetto_cmd_protos_gen",
     ":perfetto_src_perfetto_cmd_trigger_producer",
@@ -913,6 +923,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -943,6 +955,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
   defaults: [
@@ -981,6 +994,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -1027,23 +1042,26 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_test_support",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
     ":perfetto_src_ipc_host",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_ipc_perfetto_ipc",
+    ":perfetto_src_kallsyms_kallsyms",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_traced_probes_android_log_android_log",
     ":perfetto_src_traced_probes_common_common",
     ":perfetto_src_traced_probes_data_source",
     ":perfetto_src_traced_probes_filesystem_filesystem",
-    ":perfetto_src_traced_probes_ftrace_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_format_parser",
     ":perfetto_src_traced_probes_ftrace_ftrace",
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -1063,7 +1081,6 @@
     "test/cts/heapprofd_java_test_cts.cc",
     "test/cts/heapprofd_test_cts.cc",
     "test/cts/traced_perf_test_cts.cc",
-    "test/cts/utils.cc",
   ],
   static_libs: [
     "libgmock",
@@ -1089,6 +1106,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -1133,6 +1152,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   export_generated_headers: [
     "perfetto_protos_perfetto_common_cpp_gen_headers",
@@ -1146,6 +1166,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -1190,6 +1212,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -1226,6 +1249,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -1272,23 +1297,26 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_test_support",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
     ":perfetto_src_ipc_host",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_ipc_perfetto_ipc",
+    ":perfetto_src_kallsyms_kallsyms",
     ":perfetto_src_protozero_protozero",
     ":perfetto_src_traced_probes_android_log_android_log",
     ":perfetto_src_traced_probes_common_common",
     ":perfetto_src_traced_probes_data_source",
     ":perfetto_src_traced_probes_filesystem_filesystem",
-    ":perfetto_src_traced_probes_ftrace_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_format_parser",
     ":perfetto_src_traced_probes_ftrace_ftrace",
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -1319,6 +1347,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -1363,6 +1393,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   export_generated_headers: [
     "perfetto_protos_perfetto_common_cpp_gen_headers",
@@ -1376,6 +1407,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -1420,6 +1453,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -1499,6 +1533,11 @@
   name: "perfetto_include_perfetto_ext_trace_processor_export_json",
 }
 
+// GN: //include/perfetto/ext/trace_processor/importers/memory_tracker:memory_tracker
+filegroup {
+  name: "perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
+}
+
 // GN: //include/perfetto/ext/traced:sys_stats_counters
 filegroup {
   name: "perfetto_include_perfetto_ext_traced_sys_stats_counters",
@@ -1601,6 +1640,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -1647,17 +1688,20 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_test_support",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
     ":perfetto_src_ipc_host",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_ipc_perfetto_ipc",
+    ":perfetto_src_kallsyms_kallsyms",
     ":perfetto_src_profiling_common_callstack_trie",
     ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_interning_output",
     ":perfetto_src_profiling_common_proc_utils",
+    ":perfetto_src_profiling_common_profiler_guardrails",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_memory_client",
     ":perfetto_src_profiling_memory_daemon",
@@ -1670,13 +1714,14 @@
     ":perfetto_src_traced_probes_common_common",
     ":perfetto_src_traced_probes_data_source",
     ":perfetto_src_traced_probes_filesystem_filesystem",
-    ":perfetto_src_traced_probes_ftrace_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_format_parser",
     ":perfetto_src_traced_probes_ftrace_ftrace",
     ":perfetto_src_traced_probes_ftrace_integrationtests",
     ":perfetto_src_traced_probes_ftrace_test_support",
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -1725,6 +1770,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -1769,6 +1816,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -1777,6 +1825,13 @@
     "-DGOOGLE_PROTOBUF_NO_RTTI",
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ],
+  header_libs: [
+    "bionic_libc_platform_headers",
+  ],
+  require_root: true,
+  test_suites: [
+    "general-tests",
+  ],
 }
 
 // GN: //protos/perfetto/common:cpp
@@ -1789,6 +1844,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -1808,6 +1864,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.gen.cc",
     "external/perfetto/protos/perfetto/common/descriptor.gen.cc",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.gen.cc",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.gen.cc",
     "external/perfetto/protos/perfetto/common/observable_events.gen.cc",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.gen.cc",
     "external/perfetto/protos/perfetto/common/trace_stats.gen.cc",
@@ -1827,6 +1884,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -1846,6 +1904,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.gen.h",
     "external/perfetto/protos/perfetto/common/descriptor.gen.h",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.gen.h",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.gen.h",
     "external/perfetto/protos/perfetto/common/observable_events.gen.h",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.gen.h",
     "external/perfetto/protos/perfetto/common/trace_stats.gen.h",
@@ -1869,6 +1928,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -1887,6 +1947,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.pb.cc",
     "external/perfetto/protos/perfetto/common/descriptor.pb.cc",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.pb.cc",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.pb.cc",
     "external/perfetto/protos/perfetto/common/observable_events.pb.cc",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.cc",
     "external/perfetto/protos/perfetto/common/trace_stats.pb.cc",
@@ -1906,6 +1967,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -1924,6 +1986,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.pb.h",
     "external/perfetto/protos/perfetto/common/descriptor.pb.h",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.pb.h",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.pb.h",
     "external/perfetto/protos/perfetto/common/observable_events.pb.h",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pb.h",
     "external/perfetto/protos/perfetto/common/trace_stats.pb.h",
@@ -1947,6 +2010,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -1966,6 +2030,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.pbzero.cc",
     "external/perfetto/protos/perfetto/common/descriptor.pbzero.cc",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.pbzero.cc",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.pbzero.cc",
     "external/perfetto/protos/perfetto/common/observable_events.pbzero.cc",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.cc",
     "external/perfetto/protos/perfetto/common/trace_stats.pbzero.cc",
@@ -1985,6 +2050,7 @@
     "protos/perfetto/common/data_source_descriptor.proto",
     "protos/perfetto/common/descriptor.proto",
     "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
     "protos/perfetto/common/observable_events.proto",
     "protos/perfetto/common/sys_stats_counters.proto",
     "protos/perfetto/common/trace_stats.proto",
@@ -2004,6 +2070,7 @@
     "external/perfetto/protos/perfetto/common/data_source_descriptor.pbzero.h",
     "external/perfetto/protos/perfetto/common/descriptor.pbzero.h",
     "external/perfetto/protos/perfetto/common/gpu_counter_descriptor.pbzero.h",
+    "external/perfetto/protos/perfetto/common/interceptor_descriptor.pbzero.h",
     "external/perfetto/protos/perfetto/common/observable_events.pbzero.h",
     "external/perfetto/protos/perfetto/common/sys_stats_counters.pbzero.h",
     "external/perfetto/protos/perfetto/common/trace_stats.pbzero.h",
@@ -2153,6 +2220,8 @@
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -2164,6 +2233,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.cc",
     "external/perfetto/protos/perfetto/config/data_source_config.gen.cc",
+    "external/perfetto/protos/perfetto/config/interceptor_config.gen.cc",
+    "external/perfetto/protos/perfetto/config/stress_test_config.gen.cc",
     "external/perfetto/protos/perfetto/config/test_config.gen.cc",
     "external/perfetto/protos/perfetto/config/trace_config.gen.cc",
   ],
@@ -2175,6 +2246,8 @@
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -2186,6 +2259,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h",
     "external/perfetto/protos/perfetto/config/data_source_config.gen.h",
+    "external/perfetto/protos/perfetto/config/interceptor_config.gen.h",
+    "external/perfetto/protos/perfetto/config/stress_test_config.gen.h",
     "external/perfetto/protos/perfetto/config/test_config.gen.h",
     "external/perfetto/protos/perfetto/config/trace_config.gen.h",
   ],
@@ -2195,6 +2270,54 @@
   ],
 }
 
+// GN: //protos/perfetto/config:descriptor
+genrule {
+  name: "perfetto_protos_perfetto_config_descriptor",
+  srcs: [
+    "protos/perfetto/common/android_log_constants.proto",
+    "protos/perfetto/common/builtin_clock.proto",
+    "protos/perfetto/common/commit_data_request.proto",
+    "protos/perfetto/common/data_source_descriptor.proto",
+    "protos/perfetto/common/descriptor.proto",
+    "protos/perfetto/common/gpu_counter_descriptor.proto",
+    "protos/perfetto/common/interceptor_descriptor.proto",
+    "protos/perfetto/common/observable_events.proto",
+    "protos/perfetto/common/sys_stats_counters.proto",
+    "protos/perfetto/common/trace_stats.proto",
+    "protos/perfetto/common/tracing_service_capabilities.proto",
+    "protos/perfetto/common/tracing_service_state.proto",
+    "protos/perfetto/common/track_event_descriptor.proto",
+    "protos/perfetto/config/android/android_log_config.proto",
+    "protos/perfetto/config/android/android_polled_state_config.proto",
+    "protos/perfetto/config/android/packages_list_config.proto",
+    "protos/perfetto/config/chrome/chrome_config.proto",
+    "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/ftrace/ftrace_config.proto",
+    "protos/perfetto/config/gpu/gpu_counter_config.proto",
+    "protos/perfetto/config/gpu/vulkan_memory_config.proto",
+    "protos/perfetto/config/inode_file/inode_file_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/interceptors/console_config.proto",
+    "protos/perfetto/config/power/android_power_config.proto",
+    "protos/perfetto/config/process_stats/process_stats_config.proto",
+    "protos/perfetto/config/profiling/heapprofd_config.proto",
+    "protos/perfetto/config/profiling/java_hprof_config.proto",
+    "protos/perfetto/config/profiling/perf_event_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
+    "protos/perfetto/config/sys_stats/sys_stats_config.proto",
+    "protos/perfetto/config/test_config.proto",
+    "protos/perfetto/config/trace_config.proto",
+    "protos/perfetto/config/track_event/track_event_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)",
+  out: [
+    "perfetto_protos_perfetto_config_descriptor.bin",
+  ],
+}
+
 // GN: //protos/perfetto/config/ftrace:cpp
 genrule {
   name: "perfetto_protos_perfetto_config_ftrace_cpp_gen",
@@ -2525,12 +2648,120 @@
   ],
 }
 
+// GN: //protos/perfetto/config/interceptors:cpp
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_cpp_gen",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.gen.cc",
+  ],
+}
+
+// GN: //protos/perfetto/config/interceptors:cpp
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_cppgen_plugin",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.gen.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
+// GN: //protos/perfetto/config/interceptors:lite
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_lite_gen",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.pb.cc",
+  ],
+}
+
+// GN: //protos/perfetto/config/interceptors:lite
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_lite_gen_headers",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.pb.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
+// GN: //protos/perfetto/config/interceptors:zero
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_zero_gen",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "protozero_plugin",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.pbzero.cc",
+  ],
+}
+
+// GN: //protos/perfetto/config/interceptors:zero
+genrule {
+  name: "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
+  srcs: [
+    "protos/perfetto/config/interceptors/console_config.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "protozero_plugin",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/config/interceptors/console_config.pbzero.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
 // GN: //protos/perfetto/config:lite
 genrule {
   name: "perfetto_protos_perfetto_config_lite_gen",
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -2541,6 +2772,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.cc",
     "external/perfetto/protos/perfetto/config/data_source_config.pb.cc",
+    "external/perfetto/protos/perfetto/config/interceptor_config.pb.cc",
+    "external/perfetto/protos/perfetto/config/stress_test_config.pb.cc",
     "external/perfetto/protos/perfetto/config/test_config.pb.cc",
     "external/perfetto/protos/perfetto/config/trace_config.pb.cc",
   ],
@@ -2552,6 +2785,8 @@
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -2562,6 +2797,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.pb.h",
     "external/perfetto/protos/perfetto/config/data_source_config.pb.h",
+    "external/perfetto/protos/perfetto/config/interceptor_config.pb.h",
+    "external/perfetto/protos/perfetto/config/stress_test_config.pb.h",
     "external/perfetto/protos/perfetto/config/test_config.pb.h",
     "external/perfetto/protos/perfetto/config/trace_config.pb.h",
   ],
@@ -3131,6 +3368,8 @@
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -3142,6 +3381,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/data_source_config.pbzero.cc",
+    "external/perfetto/protos/perfetto/config/interceptor_config.pbzero.cc",
+    "external/perfetto/protos/perfetto/config/stress_test_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/test_config.pbzero.cc",
     "external/perfetto/protos/perfetto/config/trace_config.pbzero.cc",
   ],
@@ -3153,6 +3394,8 @@
   srcs: [
     "protos/perfetto/config/chrome/chrome_config.proto",
     "protos/perfetto/config/data_source_config.proto",
+    "protos/perfetto/config/interceptor_config.proto",
+    "protos/perfetto/config/stress_test_config.proto",
     "protos/perfetto/config/test_config.proto",
     "protos/perfetto/config/trace_config.proto",
   ],
@@ -3164,6 +3407,8 @@
   out: [
     "external/perfetto/protos/perfetto/config/chrome/chrome_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/data_source_config.pbzero.h",
+    "external/perfetto/protos/perfetto/config/interceptor_config.pbzero.h",
+    "external/perfetto/protos/perfetto/config/stress_test_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/test_config.pbzero.h",
     "external/perfetto/protos/perfetto/config/trace_config.pbzero.h",
   ],
@@ -3289,11 +3534,89 @@
   ],
 }
 
+// GN: //protos/perfetto/metrics/chrome:descriptor
+genrule {
+  name: "perfetto_protos_perfetto_metrics_chrome_descriptor",
+  srcs: [
+    "protos/perfetto/metrics/android/batt_metric.proto",
+    "protos/perfetto/metrics/android/cpu_metric.proto",
+    "protos/perfetto/metrics/android/display_metrics.proto",
+    "protos/perfetto/metrics/android/gpu_metric.proto",
+    "protos/perfetto/metrics/android/heap_profile_callsites.proto",
+    "protos/perfetto/metrics/android/hwui_metric.proto",
+    "protos/perfetto/metrics/android/ion_metric.proto",
+    "protos/perfetto/metrics/android/java_heap_histogram.proto",
+    "protos/perfetto/metrics/android/java_heap_stats.proto",
+    "protos/perfetto/metrics/android/lmk_metric.proto",
+    "protos/perfetto/metrics/android/lmk_reason_metric.proto",
+    "protos/perfetto/metrics/android/mem_metric.proto",
+    "protos/perfetto/metrics/android/mem_unagg_metric.proto",
+    "protos/perfetto/metrics/android/package_list.proto",
+    "protos/perfetto/metrics/android/powrails_metric.proto",
+    "protos/perfetto/metrics/android/process_metadata.proto",
+    "protos/perfetto/metrics/android/startup_metric.proto",
+    "protos/perfetto/metrics/android/surfaceflinger.proto",
+    "protos/perfetto/metrics/android/sysui_cuj_metrics.proto",
+    "protos/perfetto/metrics/android/task_names.proto",
+    "protos/perfetto/metrics/android/thread_time_in_state_metric.proto",
+    "protos/perfetto/metrics/android/unsymbolized_frames.proto",
+    "protos/perfetto/metrics/chrome/all_chrome_metrics.proto",
+    "protos/perfetto/metrics/chrome/test_chrome_metric.proto",
+    "protos/perfetto/metrics/custom_options.proto",
+    "protos/perfetto/metrics/metrics.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --proto_path=external/protobuf/src --descriptor_set_out=$(out) $(in)",
+  out: [
+    "perfetto_protos_perfetto_metrics_chrome_descriptor.bin",
+  ],
+}
+
+// GN: //protos/perfetto/metrics:descriptor
+genrule {
+  name: "perfetto_protos_perfetto_metrics_descriptor",
+  srcs: [
+    "protos/perfetto/metrics/android/batt_metric.proto",
+    "protos/perfetto/metrics/android/cpu_metric.proto",
+    "protos/perfetto/metrics/android/display_metrics.proto",
+    "protos/perfetto/metrics/android/gpu_metric.proto",
+    "protos/perfetto/metrics/android/heap_profile_callsites.proto",
+    "protos/perfetto/metrics/android/hwui_metric.proto",
+    "protos/perfetto/metrics/android/ion_metric.proto",
+    "protos/perfetto/metrics/android/java_heap_histogram.proto",
+    "protos/perfetto/metrics/android/java_heap_stats.proto",
+    "protos/perfetto/metrics/android/lmk_metric.proto",
+    "protos/perfetto/metrics/android/lmk_reason_metric.proto",
+    "protos/perfetto/metrics/android/mem_metric.proto",
+    "protos/perfetto/metrics/android/mem_unagg_metric.proto",
+    "protos/perfetto/metrics/android/package_list.proto",
+    "protos/perfetto/metrics/android/powrails_metric.proto",
+    "protos/perfetto/metrics/android/process_metadata.proto",
+    "protos/perfetto/metrics/android/startup_metric.proto",
+    "protos/perfetto/metrics/android/surfaceflinger.proto",
+    "protos/perfetto/metrics/android/sysui_cuj_metrics.proto",
+    "protos/perfetto/metrics/android/task_names.proto",
+    "protos/perfetto/metrics/android/thread_time_in_state_metric.proto",
+    "protos/perfetto/metrics/android/unsymbolized_frames.proto",
+    "protos/perfetto/metrics/metrics.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --descriptor_set_out=$(out) $(in)",
+  out: [
+    "perfetto_protos_perfetto_metrics_descriptor.bin",
+  ],
+}
+
 // GN: //protos/perfetto/trace/android:cpp
 genrule {
   name: "perfetto_protos_perfetto_trace_android_cpp_gen",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3306,6 +3629,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.gen.cc",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.gen.cc",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.gen.cc",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.gen.cc",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.cc",
@@ -3318,6 +3642,7 @@
   name: "perfetto_protos_perfetto_trace_android_cpp_gen_headers",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3330,6 +3655,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.gen.h",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.gen.h",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.gen.h",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.gen.h",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.gen.h",
@@ -3346,6 +3672,7 @@
   name: "perfetto_protos_perfetto_trace_android_lite_gen",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3357,6 +3684,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.pb.cc",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.cc",
@@ -3369,6 +3697,7 @@
   name: "perfetto_protos_perfetto_trace_android_lite_gen_headers",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3380,6 +3709,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.pb.h",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.pb.h",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.pb.h",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.pb.h",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.pb.h",
@@ -3396,6 +3726,7 @@
   name: "perfetto_protos_perfetto_trace_android_zero_gen",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3408,6 +3739,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.cc",
@@ -3420,6 +3752,7 @@
   name: "perfetto_protos_perfetto_trace_android_zero_gen_headers",
   srcs: [
     "protos/perfetto/trace/android/android_log.proto",
+    "protos/perfetto/trace/android/frame_timeline_event.proto",
     "protos/perfetto/trace/android/gpu_mem_event.proto",
     "protos/perfetto/trace/android/graphics_frame_event.proto",
     "protos/perfetto/trace/android/initial_display_state.proto",
@@ -3432,6 +3765,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/android/android_log.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/android/frame_timeline_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/android/gpu_mem_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/android/graphics_frame_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/android/initial_display_state.pbzero.h",
@@ -3688,14 +4022,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -3733,14 +4071,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.gen.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.gen.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.gen.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.gen.cc",
@@ -3778,14 +4120,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -3823,14 +4169,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.gen.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.gen.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.gen.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.gen.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.gen.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.gen.h",
@@ -3872,14 +4222,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -3916,14 +4270,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.pb.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pb.cc",
@@ -3961,14 +4319,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -4005,14 +4367,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.pb.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pb.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.pb.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pb.h",
@@ -4054,14 +4420,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -4099,14 +4469,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pbzero.cc",
@@ -4144,14 +4518,18 @@
     "protos/perfetto/trace/ftrace/cgroup.proto",
     "protos/perfetto/trace/ftrace/clk.proto",
     "protos/perfetto/trace/ftrace/compaction.proto",
+    "protos/perfetto/trace/ftrace/cpuhp.proto",
+    "protos/perfetto/trace/ftrace/dpu.proto",
     "protos/perfetto/trace/ftrace/ext4.proto",
     "protos/perfetto/trace/ftrace/f2fs.proto",
+    "protos/perfetto/trace/ftrace/fastrpc.proto",
     "protos/perfetto/trace/ftrace/fence.proto",
     "protos/perfetto/trace/ftrace/filemap.proto",
     "protos/perfetto/trace/ftrace/ftrace.proto",
     "protos/perfetto/trace/ftrace/ftrace_event.proto",
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+    "protos/perfetto/trace/ftrace/g2d.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
     "protos/perfetto/trace/ftrace/gpu_mem.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
@@ -4189,14 +4567,18 @@
     "external/perfetto/protos/perfetto/trace/ftrace/cgroup.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/clk.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/compaction.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/cpuhp.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/dpu.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ext4.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/f2fs.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/fastrpc.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/fence.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/filemap.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/ftrace/g2d.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/generic.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/gpu_mem.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pbzero.h",
@@ -4624,6 +5006,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4636,6 +5019,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.gen.cc",
+    "external/perfetto/protos/perfetto/trace/memory_graph.gen.cc",
     "external/perfetto/protos/perfetto/trace/test_event.gen.cc",
     "external/perfetto/protos/perfetto/trace/trace.gen.cc",
     "external/perfetto/protos/perfetto/trace/trace_packet.gen.cc",
@@ -4648,6 +5032,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_cpp_gen_headers",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4660,6 +5045,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.gen.h",
+    "external/perfetto/protos/perfetto/trace/memory_graph.gen.h",
     "external/perfetto/protos/perfetto/trace/test_event.gen.h",
     "external/perfetto/protos/perfetto/trace/trace.gen.h",
     "external/perfetto/protos/perfetto/trace/trace_packet.gen.h",
@@ -4676,6 +5062,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_lite_gen",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4687,6 +5074,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.pb.cc",
+    "external/perfetto/protos/perfetto/trace/memory_graph.pb.cc",
     "external/perfetto/protos/perfetto/trace/test_event.pb.cc",
     "external/perfetto/protos/perfetto/trace/trace.pb.cc",
     "external/perfetto/protos/perfetto/trace/trace_packet.pb.cc",
@@ -4699,6 +5087,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_lite_gen_headers",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4710,6 +5099,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.pb.h",
+    "external/perfetto/protos/perfetto/trace/memory_graph.pb.h",
     "external/perfetto/protos/perfetto/trace/test_event.pb.h",
     "external/perfetto/protos/perfetto/trace/trace.pb.h",
     "external/perfetto/protos/perfetto/trace/trace_packet.pb.h",
@@ -4726,6 +5116,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_zero_gen",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4738,6 +5129,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/memory_graph.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/test_event.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/trace.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/trace_packet.pbzero.cc",
@@ -4750,6 +5142,7 @@
   name: "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
   srcs: [
     "protos/perfetto/trace/extension_descriptor.proto",
+    "protos/perfetto/trace/memory_graph.proto",
     "protos/perfetto/trace/test_event.proto",
     "protos/perfetto/trace/trace.proto",
     "protos/perfetto/trace/trace_packet.proto",
@@ -4762,6 +5155,7 @@
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
     "external/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/memory_graph.pbzero.h",
     "external/perfetto/protos/perfetto/trace/test_event.pbzero.h",
     "external/perfetto/protos/perfetto/trace/trace.pbzero.h",
     "external/perfetto/protos/perfetto/trace/trace_packet.pbzero.h",
@@ -5085,6 +5479,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_cpp_gen",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5096,6 +5491,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.gen.cc",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.gen.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.gen.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.gen.cc",
@@ -5107,6 +5503,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5118,6 +5515,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.gen.h",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.gen.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.gen.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.gen.h",
@@ -5133,6 +5531,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_lite_gen",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5143,6 +5542,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pb.cc",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pb.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.pb.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pb.cc",
@@ -5154,6 +5554,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5164,6 +5565,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pb.h",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pb.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.pb.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pb.h",
@@ -5179,6 +5581,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_zero_gen",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5190,6 +5593,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.cc",
@@ -5201,6 +5605,7 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
   srcs: [
+    "protos/perfetto/trace/profiling/deobfuscation.proto",
     "protos/perfetto/trace/profiling/heap_graph.proto",
     "protos/perfetto/trace/profiling/profile_common.proto",
     "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -5212,6 +5617,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/profiling/deobfuscation.pbzero.h",
     "external/perfetto/protos/perfetto/trace/profiling/heap_graph.pbzero.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h",
     "external/perfetto/protos/perfetto/trace/profiling/profile_packet.pbzero.h",
@@ -5557,15 +5963,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_cpp_gen",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5582,15 +5993,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.gen.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.gen.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.gen.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.gen.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.cc",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.gen.cc",
@@ -5607,15 +6023,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5632,15 +6053,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_cppgen_plugin) --plugin_out=wrapper_namespace=gen:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.gen.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.gen.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.gen.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.gen.h",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.gen.h",
@@ -5661,15 +6087,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_lite_gen",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5685,15 +6116,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.pb.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pb.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pb.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.pb.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.cc",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.pb.cc",
@@ -5710,15 +6146,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_lite_gen_headers",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5734,15 +6175,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.pb.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pb.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pb.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.pb.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pb.h",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.pb.h",
@@ -5763,15 +6209,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_zero_gen",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5788,15 +6239,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.pbzero.cc",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.cc",
@@ -5813,15 +6269,20 @@
 genrule {
   name: "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
   srcs: [
+    "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
     "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
     "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
     "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
     "protos/perfetto/trace/track_event/chrome_latency_info.proto",
     "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+    "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+    "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
     "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+    "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
     "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
     "protos/perfetto/trace/track_event/chrome_user_event.proto",
+    "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
     "protos/perfetto/trace/track_event/counter_descriptor.proto",
     "protos/perfetto/trace/track_event/debug_annotation.proto",
     "protos/perfetto/trace/track_event/log_message.proto",
@@ -5838,15 +6299,20 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
   out: [
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_application_state_info.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_keyed_service.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/chrome_user_event.pbzero.h",
+    "external/perfetto/protos/perfetto/trace/track_event/chrome_window_handle_event_info.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/counter_descriptor.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/debug_annotation.pbzero.h",
     "external/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.h",
@@ -5904,7 +6370,6 @@
   name: "perfetto_src_android_internal_android_internal",
   srcs: [
     "src/android_internal/atrace_hal.cc",
-    "src/android_internal/dropbox_service.cc",
     "src/android_internal/health_hal.cc",
     "src/android_internal/incident_service.cc",
     "src/android_internal/power_stats_hal.cc",
@@ -5925,6 +6390,19 @@
   ],
 }
 
+// GN: //src/android_stats:android_stats
+filegroup {
+  name: "perfetto_src_android_stats_android_stats",
+  srcs: [
+    "src/android_stats/statsd_logging_helper.cc",
+  ],
+}
+
+// GN: //src/android_stats:perfetto_atoms
+filegroup {
+  name: "perfetto_src_android_stats_perfetto_atoms",
+}
+
 // GN: //src/base:base
 filegroup {
   name: "perfetto_src_base_base",
@@ -5935,16 +6413,19 @@
     "src/base/metatrace.cc",
     "src/base/paged_memory.cc",
     "src/base/pipe.cc",
+    "src/base/status.cc",
     "src/base/string_splitter.cc",
     "src/base/string_utils.cc",
     "src/base/string_view.cc",
-    "src/base/subprocess.cc",
+    "src/base/subprocess_posix.cc",
     "src/base/temp_file.cc",
     "src/base/thread_checker.cc",
     "src/base/thread_task_runner.cc",
     "src/base/time.cc",
     "src/base/unix_task_runner.cc",
+    "src/base/utils.cc",
     "src/base/uuid.cc",
+    "src/base/version.cc",
     "src/base/virtual_destructors.cc",
     "src/base/waitable_event.cc",
     "src/base/watchdog_posix.cc",
@@ -5985,6 +6466,7 @@
     "src/base/unix_socket_unittest.cc",
     "src/base/utils_unittest.cc",
     "src/base/uuid_unittest.cc",
+    "src/base/watchdog_posix_unittest.cc",
     "src/base/watchdog_unittest.cc",
     "src/base/weak_ptr_unittest.cc",
   ],
@@ -5998,6 +6480,21 @@
   ],
 }
 
+// GN: //src/base:version_gen_h
+genrule {
+  name: "perfetto_src_base_version_gen_h",
+  srcs: [
+    "CHANGELOG",
+  ],
+  cmd: "python3 $(location tools/write_version_header.py) --no_git --changelog=$(location CHANGELOG) --cpp_out=$(out)",
+  out: [
+    "perfetto_version.gen.h",
+  ],
+  tool_files: [
+    "tools/write_version_header.py",
+  ],
+}
+
 // GN: //src/ipc:client
 filegroup {
   name: "perfetto_src_ipc_client",
@@ -6025,6 +6522,11 @@
   ],
 }
 
+// GN: //src/ipc:perfetto_ipc
+filegroup {
+  name: "perfetto_src_ipc_perfetto_ipc",
+}
+
 // GN: //src/ipc:test_messages_cpp
 genrule {
   name: "perfetto_src_ipc_test_messages_cpp_gen",
@@ -6125,9 +6627,22 @@
   ],
 }
 
-// GN: //src/perfetto_cmd:perfetto_atoms
+// GN: //src/kallsyms:kallsyms
 filegroup {
-  name: "perfetto_src_perfetto_cmd_perfetto_atoms",
+  name: "perfetto_src_kallsyms_kallsyms",
+  srcs: [
+    "src/kallsyms/kernel_symbol_map.cc",
+    "src/kallsyms/lazy_kernel_symbolizer.cc",
+  ],
+}
+
+// GN: //src/kallsyms:unittests
+filegroup {
+  name: "perfetto_src_kallsyms_unittests",
+  srcs: [
+    "src/kallsyms/kernel_symbol_map_unittest.cc",
+    "src/kallsyms/lazy_kernel_symbolizer_unittest.cc",
+  ],
 }
 
 // GN: //src/perfetto_cmd:perfetto_cmd
@@ -6235,12 +6750,21 @@
   ],
 }
 
+// GN: //src/profiling/common:profiler_guardrails
+filegroup {
+  name: "perfetto_src_profiling_common_profiler_guardrails",
+  srcs: [
+    "src/profiling/common/profiler_guardrails.cc",
+  ],
+}
+
 // GN: //src/profiling/common:unittests
 filegroup {
   name: "perfetto_src_profiling_common_unittests",
   srcs: [
     "src/profiling/common/interner_unittest.cc",
     "src/profiling/common/proc_utils_unittest.cc",
+    "src/profiling/common/profiler_guardrails_unittest.cc",
   ],
 }
 
@@ -6260,19 +6784,33 @@
   ],
 }
 
+// GN: //src/profiling/memory:bionic_libc_platform_headers_on_android
+filegroup {
+  name: "perfetto_src_profiling_memory_bionic_libc_platform_headers_on_android",
+}
+
 // GN: //src/profiling/memory:client
 filegroup {
   name: "perfetto_src_profiling_memory_client",
   srcs: [
     "src/profiling/memory/client.cc",
+    "src/profiling/memory/sampler.cc",
   ],
 }
 
-// GN: //src/profiling/memory:client_ext
+// GN: //src/profiling/memory:client_api
 filegroup {
-  name: "perfetto_src_profiling_memory_client_ext",
+  name: "perfetto_src_profiling_memory_client_api",
   srcs: [
-    "src/profiling/memory/client_ext.cc",
+    "src/profiling/memory/client_api.cc",
+  ],
+}
+
+// GN: //src/profiling/memory:client_api_standalone
+filegroup {
+  name: "perfetto_src_profiling_memory_client_api_standalone",
+  srcs: [
+    "src/profiling/memory/client_api_standalone.cc",
   ],
 }
 
@@ -6284,7 +6822,6 @@
     "src/profiling/memory/bookkeeping_dump.cc",
     "src/profiling/memory/heapprofd_producer.cc",
     "src/profiling/memory/java_hprof_producer.cc",
-    "src/profiling/memory/page_idle_checker.cc",
     "src/profiling/memory/system_property.cc",
     "src/profiling/memory/unwinding.cc",
   ],
@@ -6337,7 +6874,6 @@
     "src/profiling/memory/bookkeeping_unittest.cc",
     "src/profiling/memory/client_unittest.cc",
     "src/profiling/memory/heapprofd_producer_unittest.cc",
-    "src/profiling/memory/page_idle_checker_unittest.cc",
     "src/profiling/memory/parse_smaps_unittest.cc",
     "src/profiling/memory/sampler_unittest.cc",
     "src/profiling/memory/system_property_unittest.cc",
@@ -6354,6 +6890,14 @@
   ],
 }
 
+// GN: //src/profiling/memory:wrap_allocators
+filegroup {
+  name: "perfetto_src_profiling_memory_wrap_allocators",
+  srcs: [
+    "src/profiling/memory/wrap_allocators.cc",
+  ],
+}
+
 // GN: //src/profiling/perf:common_types
 filegroup {
   name: "perfetto_src_profiling_perf_common_types",
@@ -6422,7 +6966,13 @@
 filegroup {
   name: "perfetto_src_profiling_symbolizer_symbolizer",
   srcs: [
+    "src/profiling/symbolizer/filesystem_posix.cc",
+    "src/profiling/symbolizer/filesystem_windows.cc",
     "src/profiling/symbolizer/local_symbolizer.cc",
+    "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
+    "src/profiling/symbolizer/scoped_read_mmap_windows.cc",
+    "src/profiling/symbolizer/subprocess_posix.cc",
+    "src/profiling/symbolizer/subprocess_windows.cc",
     "src/profiling/symbolizer/symbolizer.cc",
   ],
 }
@@ -6455,6 +7005,9 @@
   static_libs: [
     "libprotoc",
   ],
+  generated_headers: [
+    "perfetto_src_base_version_gen_h",
+  ],
   defaults: [
     "perfetto_defaults",
   ],
@@ -6470,6 +7023,7 @@
   srcs: [
     "src/protozero/field.cc",
     "src/protozero/message.cc",
+    "src/protozero/message_arena.cc",
     "src/protozero/message_handle.cc",
     "src/protozero/packed_repeated_fields.cc",
     "src/protozero/proto_decoder.cc",
@@ -6724,6 +7278,7 @@
     "src/trace_processor/importers/common/args_tracker.cc",
     "src/trace_processor/importers/common/clock_tracker.cc",
     "src/trace_processor/importers/common/event_tracker.cc",
+    "src/trace_processor/importers/common/flow_tracker.cc",
     "src/trace_processor/importers/common/global_args_tracker.cc",
     "src/trace_processor/importers/common/process_tracker.cc",
     "src/trace_processor/importers/common/slice_tracker.cc",
@@ -6732,12 +7287,40 @@
   ],
 }
 
+// GN: //src/trace_processor/importers:gen_cc_config_descriptor
+genrule {
+  name: "perfetto_src_trace_processor_importers_gen_cc_config_descriptor",
+  srcs: [
+    ":perfetto_protos_perfetto_config_descriptor",
+  ],
+  cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)",
+  out: [
+    "src/trace_processor/importers/config.descriptor.h",
+  ],
+  tool_files: [
+    "tools/gen_cc_proto_descriptor.py",
+  ],
+}
+
+// GN: //src/trace_processor/importers/memory_tracker:graph_processor
+filegroup {
+  name: "perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
+  srcs: [
+    "src/trace_processor/importers/memory_tracker/graph.cc",
+    "src/trace_processor/importers/memory_tracker/graph_processor.cc",
+    "src/trace_processor/importers/memory_tracker/memory_allocator_node_id.cc",
+    "src/trace_processor/importers/memory_tracker/raw_memory_graph_node.cc",
+    "src/trace_processor/importers/memory_tracker/raw_process_memory_node.cc",
+  ],
+}
+
 // GN: //src/trace_processor/importers:unittests
 filegroup {
   name: "perfetto_src_trace_processor_importers_unittests",
   srcs: [
     "src/trace_processor/importers/common/clock_tracker_unittest.cc",
     "src/trace_processor/importers/common/event_tracker_unittest.cc",
+    "src/trace_processor/importers/common/flow_tracker_unittest.cc",
     "src/trace_processor/importers/common/process_tracker_unittest.cc",
     "src/trace_processor/importers/common/slice_tracker_unittest.cc",
   ],
@@ -6747,12 +7330,15 @@
 filegroup {
   name: "perfetto_src_trace_processor_lib",
   srcs: [
-    "src/trace_processor/dynamic/ancestor_slice_generator.cc",
+    "src/trace_processor/dynamic/ancestor_generator.cc",
+    "src/trace_processor/dynamic/connected_flow_generator.cc",
     "src/trace_processor/dynamic/descendant_slice_generator.cc",
     "src/trace_processor/dynamic/describe_slice_generator.cc",
     "src/trace_processor/dynamic/experimental_counter_dur_generator.cc",
     "src/trace_processor/dynamic/experimental_flamegraph_generator.cc",
+    "src/trace_processor/dynamic/experimental_sched_upid_generator.cc",
     "src/trace_processor/dynamic/experimental_slice_layout_generator.cc",
+    "src/trace_processor/dynamic/thread_state_generator.cc",
     "src/trace_processor/iterator_impl.cc",
     "src/trace_processor/read_trace.cc",
     "src/trace_processor/trace_processor.cc",
@@ -6768,6 +7354,110 @@
   ],
 }
 
+// GN: //src/trace_processor/metrics:gen_cc_all_chrome_metrics_descriptor
+genrule {
+  name: "perfetto_src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+  srcs: [
+    ":perfetto_protos_perfetto_metrics_chrome_descriptor",
+  ],
+  cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)",
+  out: [
+    "src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h",
+  ],
+  tool_files: [
+    "tools/gen_cc_proto_descriptor.py",
+  ],
+}
+
+// GN: //src/trace_processor/metrics:gen_cc_metrics_descriptor
+genrule {
+  name: "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
+  srcs: [
+    ":perfetto_protos_perfetto_metrics_descriptor",
+  ],
+  cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)",
+  out: [
+    "src/trace_processor/metrics/metrics.descriptor.h",
+  ],
+  tool_files: [
+    "tools/gen_cc_proto_descriptor.py",
+  ],
+}
+
+// GN: //src/trace_processor/metrics:gen_merged_sql_metrics
+genrule {
+  name: "perfetto_src_trace_processor_metrics_gen_merged_sql_metrics",
+  srcs: [
+    "src/trace_processor/metrics/android/android_batt.sql",
+    "src/trace_processor/metrics/android/android_cpu.sql",
+    "src/trace_processor/metrics/android/android_cpu_agg.sql",
+    "src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql",
+    "src/trace_processor/metrics/android/android_gpu.sql",
+    "src/trace_processor/metrics/android/android_hwui_metric.sql",
+    "src/trace_processor/metrics/android/android_ion.sql",
+    "src/trace_processor/metrics/android/android_lmk.sql",
+    "src/trace_processor/metrics/android/android_lmk_reason.sql",
+    "src/trace_processor/metrics/android/android_mem.sql",
+    "src/trace_processor/metrics/android/android_mem_unagg.sql",
+    "src/trace_processor/metrics/android/android_package_list.sql",
+    "src/trace_processor/metrics/android/android_powrails.sql",
+    "src/trace_processor/metrics/android/android_proxy_power.sql",
+    "src/trace_processor/metrics/android/android_startup.sql",
+    "src/trace_processor/metrics/android/android_startup_launches.sql",
+    "src/trace_processor/metrics/android/android_surfaceflinger.sql",
+    "src/trace_processor/metrics/android/android_sysui_cuj.sql",
+    "src/trace_processor/metrics/android/android_task_names.sql",
+    "src/trace_processor/metrics/android/android_task_state.sql",
+    "src/trace_processor/metrics/android/android_thread_time_in_state.sql",
+    "src/trace_processor/metrics/android/cpu_info.sql",
+    "src/trace_processor/metrics/android/display_metrics.sql",
+    "src/trace_processor/metrics/android/frame_missed.sql",
+    "src/trace_processor/metrics/android/global_counter_span_view.sql",
+    "src/trace_processor/metrics/android/heap_profile_callsites.sql",
+    "src/trace_processor/metrics/android/hsc_startups.sql",
+    "src/trace_processor/metrics/android/java_heap_histogram.sql",
+    "src/trace_processor/metrics/android/java_heap_stats.sql",
+    "src/trace_processor/metrics/android/mem_stats_priority_breakdown.sql",
+    "src/trace_processor/metrics/android/power_drain_in_watts.sql",
+    "src/trace_processor/metrics/android/power_profile_data.sql",
+    "src/trace_processor/metrics/android/process_counter_span_view.sql",
+    "src/trace_processor/metrics/android/process_mem.sql",
+    "src/trace_processor/metrics/android/process_metadata.sql",
+    "src/trace_processor/metrics/android/process_oom_score.sql",
+    "src/trace_processor/metrics/android/process_unagg_mem_view.sql",
+    "src/trace_processor/metrics/android/span_view_stats.sql",
+    "src/trace_processor/metrics/android/unsymbolized_frames.sql",
+    "src/trace_processor/metrics/chrome/actual_power_by_category.sql",
+    "src/trace_processor/metrics/chrome/actual_power_by_rail_mode.sql",
+    "src/trace_processor/metrics/chrome/chrome_event_metadata.sql",
+    "src/trace_processor/metrics/chrome/chrome_processes.sql",
+    "src/trace_processor/metrics/chrome/chrome_thread_slice_with_cpu_time.sql",
+    "src/trace_processor/metrics/chrome/cpu_time_by_category.sql",
+    "src/trace_processor/metrics/chrome/cpu_time_by_rail_mode.sql",
+    "src/trace_processor/metrics/chrome/estimated_power_by_category.sql",
+    "src/trace_processor/metrics/chrome/estimated_power_by_rail_mode.sql",
+    "src/trace_processor/metrics/chrome/rail_modes.sql",
+    "src/trace_processor/metrics/chrome/scroll_flow_event.sql",
+    "src/trace_processor/metrics/chrome/scroll_flow_event_queuing_delay.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank_cause.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_task.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_touch_move.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank_cause_get_bitmap.sql",
+    "src/trace_processor/metrics/chrome/scroll_jank_cause_queuing_delay.sql",
+    "src/trace_processor/metrics/chrome/test_chrome_metric.sql",
+    "src/trace_processor/metrics/trace_metadata.sql",
+    "src/trace_processor/metrics/webview/webview_power_usage.sql",
+  ],
+  cmd: "$(location tools/gen_merged_sql_metrics.py) --cpp_out=$(out) $(in)",
+  out: [
+    "src/trace_processor/metrics/sql_metrics.h",
+  ],
+  tool_files: [
+    "tools/gen_merged_sql_metrics.py",
+  ],
+}
+
 // GN: //src/trace_processor/metrics:lib
 filegroup {
   name: "perfetto_src_trace_processor_metrics_lib",
@@ -6825,6 +7515,7 @@
     "src/trace_processor/sqlite/query_constraints_unittest.cc",
     "src/trace_processor/sqlite/span_join_operator_table_unittest.cc",
     "src/trace_processor/sqlite/sqlite3_str_split_unittest.cc",
+    "src/trace_processor/sqlite/sqlite_utils_unittest.cc",
   ],
 }
 
@@ -6877,18 +7568,24 @@
     "src/trace_processor/importers/json/json_utils.cc",
     "src/trace_processor/importers/ninja/ninja_log_parser.cc",
     "src/trace_processor/importers/proto/args_table_utils.cc",
+    "src/trace_processor/importers/proto/async_track_set_tracker.cc",
     "src/trace_processor/importers/proto/heap_profile_tracker.cc",
+    "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
+    "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
     "src/trace_processor/importers/proto/metadata_tracker.cc",
     "src/trace_processor/importers/proto/packet_sequence_state.cc",
     "src/trace_processor/importers/proto/profile_module.cc",
     "src/trace_processor/importers/proto/profile_packet_utils.cc",
+    "src/trace_processor/importers/proto/profiler_util.cc",
     "src/trace_processor/importers/proto/proto_importer_module.cc",
     "src/trace_processor/importers/proto/proto_trace_parser.cc",
+    "src/trace_processor/importers/proto/proto_trace_reader.cc",
     "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
     "src/trace_processor/importers/proto/stack_profile_tracker.cc",
     "src/trace_processor/importers/proto/track_event_module.cc",
     "src/trace_processor/importers/proto/track_event_parser.cc",
     "src/trace_processor/importers/proto/track_event_tokenizer.cc",
+    "src/trace_processor/importers/proto/track_event_tracker.cc",
     "src/trace_processor/trace_processor_context.cc",
     "src/trace_processor/trace_processor_storage.cc",
     "src/trace_processor/trace_processor_storage_impl.cc",
@@ -6951,10 +7648,15 @@
   srcs: [
     "src/trace_processor/dynamic/experimental_counter_dur_generator_unittest.cc",
     "src/trace_processor/dynamic/experimental_slice_layout_generator_unittest.cc",
+    "src/trace_processor/dynamic/thread_state_generator_unittest.cc",
     "src/trace_processor/forwarding_trace_parser_unittest.cc",
     "src/trace_processor/importers/ftrace/sched_event_tracker_unittest.cc",
     "src/trace_processor/importers/fuchsia/fuchsia_trace_utils_unittest.cc",
+    "src/trace_processor/importers/memory_tracker/graph_processor_unittest.cc",
+    "src/trace_processor/importers/memory_tracker/graph_unittest.cc",
+    "src/trace_processor/importers/memory_tracker/raw_process_memory_node_unittest.cc",
     "src/trace_processor/importers/proto/args_table_utils_unittest.cc",
+    "src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc",
     "src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc",
     "src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc",
     "src/trace_processor/importers/proto/proto_trace_parser_unittest.cc",
@@ -7067,11 +7769,19 @@
   ],
 }
 
-// GN: //src/traced/probes/ftrace:format_parser
+// GN: //src/traced/probes/ftrace/format_parser:format_parser
 filegroup {
-  name: "perfetto_src_traced_probes_ftrace_format_parser",
+  name: "perfetto_src_traced_probes_ftrace_format_parser_format_parser",
   srcs: [
-    "src/traced/probes/ftrace/format_parser.cc",
+    "src/traced/probes/ftrace/format_parser/format_parser.cc",
+  ],
+}
+
+// GN: //src/traced/probes/ftrace/format_parser:unittests
+filegroup {
+  name: "perfetto_src_traced_probes_ftrace_format_parser_unittests",
+  srcs: [
+    "src/traced/probes/ftrace/format_parser/format_parser_unittest.cc",
   ],
 }
 
@@ -7093,6 +7803,7 @@
     "src/traced/probes/ftrace/ftrace_data_source.cc",
     "src/traced/probes/ftrace/ftrace_procfs.cc",
     "src/traced/probes/ftrace/ftrace_stats.cc",
+    "src/traced/probes/ftrace/printk_formats_parser.cc",
     "src/traced/probes/ftrace/proto_translation_table.cc",
   ],
 }
@@ -7105,22 +7816,6 @@
   ],
 }
 
-// GN: //src/traced/probes/ftrace/kallsyms:kallsyms
-filegroup {
-  name: "perfetto_src_traced_probes_ftrace_kallsyms_kallsyms",
-  srcs: [
-    "src/traced/probes/ftrace/kallsyms/kernel_symbol_map.cc",
-  ],
-}
-
-// GN: //src/traced/probes/ftrace/kallsyms:unittests
-filegroup {
-  name: "perfetto_src_traced_probes_ftrace_kallsyms_unittests",
-  srcs: [
-    "src/traced/probes/ftrace/kallsyms/kernel_symbol_map_unittest.cc",
-  ],
-}
-
 // GN: //src/traced/probes/ftrace:test_messages_cpp
 genrule {
   name: "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen",
@@ -7243,11 +7938,11 @@
     "src/traced/probes/ftrace/cpu_stats_parser_unittest.cc",
     "src/traced/probes/ftrace/discover_vendor_tracepoints_unittest.cc",
     "src/traced/probes/ftrace/event_info_unittest.cc",
-    "src/traced/probes/ftrace/format_parser_unittest.cc",
     "src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc",
     "src/traced/probes/ftrace/ftrace_config_unittest.cc",
     "src/traced/probes/ftrace/ftrace_controller_unittest.cc",
     "src/traced/probes/ftrace/ftrace_procfs_unittest.cc",
+    "src/traced/probes/ftrace/printk_formats_parser_unittest.cc",
     "src/traced/probes/ftrace/proto_translation_table_unittest.cc",
   ],
 }
@@ -7284,11 +7979,19 @@
   ],
 }
 
+// GN: //src/traced/probes/packages_list:packages_list_parser
+filegroup {
+  name: "perfetto_src_traced_probes_packages_list_packages_list_parser",
+  srcs: [
+    "src/traced/probes/packages_list/packages_list_parser.cc",
+  ],
+}
+
 // GN: //src/traced/probes/packages_list:unittests
 filegroup {
   name: "perfetto_src_traced_probes_packages_list_unittests",
   srcs: [
-    "src/traced/probes/packages_list/packages_list_data_source_unittest.cc",
+    "src/traced/probes/packages_list/packages_list_unittest.cc",
   ],
 }
 
@@ -7390,9 +8093,12 @@
 filegroup {
   name: "perfetto_src_tracing_client_api_without_backends",
   srcs: [
+    "src/tracing/console_interceptor.cc",
     "src/tracing/data_source.cc",
     "src/tracing/debug_annotation.cc",
     "src/tracing/event_context.cc",
+    "src/tracing/interceptor.cc",
+    "src/tracing/internal/interceptor_trace_writer.cc",
     "src/tracing/internal/tracing_muxer_impl.cc",
     "src/tracing/internal/track_event_internal.cc",
     "src/tracing/platform.cc",
@@ -7400,6 +8106,7 @@
     "src/tracing/track.cc",
     "src/tracing/track_event_category_registry.cc",
     "src/tracing/track_event_legacy.cc",
+    "src/tracing/track_event_state_tracker.cc",
     "src/tracing/virtual_destructors.cc",
   ],
 }
@@ -7588,6 +8295,7 @@
 filegroup {
   name: "perfetto_test_test_helper",
   srcs: [
+    "test/android_test_utils.cc",
     "test/fake_producer.cc",
     "test/test_helper.cc",
   ],
@@ -7608,6 +8316,7 @@
     "tools/trace_to_text/deobfuscate_profile.cc",
     "tools/trace_to_text/main.cc",
     "tools/trace_to_text/symbolize_profile.cc",
+    "tools/trace_to_text/trace_to_hprof.cc",
     "tools/trace_to_text/trace_to_json.cc",
     "tools/trace_to_text/trace_to_profile.cc",
     "tools/trace_to_text/trace_to_systrace.cc",
@@ -7648,6 +8357,7 @@
     ":perfetto_protos_perfetto_config_ftrace_lite_gen",
     ":perfetto_protos_perfetto_config_gpu_lite_gen",
     ":perfetto_protos_perfetto_config_inode_file_lite_gen",
+    ":perfetto_protos_perfetto_config_interceptors_lite_gen",
     ":perfetto_protos_perfetto_config_lite_gen",
     ":perfetto_protos_perfetto_config_power_lite_gen",
     ":perfetto_protos_perfetto_config_process_stats_lite_gen",
@@ -7684,6 +8394,7 @@
     "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_lite_gen_headers",
     "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_config_power_lite_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
@@ -7712,6 +8423,7 @@
     "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_lite_gen_headers",
     "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_config_power_lite_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
@@ -7743,8 +8455,8 @@
   ],
   apex_available: [
     "//apex_available:platform",
+    "com.android.art",
     "com.android.art.debug",
-    "com.android.art.release",
   ],
 }
 
@@ -7756,6 +8468,7 @@
     ":perfetto_include_perfetto_ext_base_base",
     ":perfetto_include_perfetto_ext_ipc_ipc",
     ":perfetto_include_perfetto_ext_trace_processor_export_json",
+    ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
     ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
     ":perfetto_include_perfetto_ext_traced_traced",
     ":perfetto_include_perfetto_ext_tracing_core_core",
@@ -7780,6 +8493,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -7828,6 +8543,8 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_android_stats",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_test_support",
     ":perfetto_src_base_unittests",
@@ -7838,7 +8555,8 @@
     ":perfetto_src_ipc_test_messages_cpp_gen",
     ":perfetto_src_ipc_test_messages_ipc_gen",
     ":perfetto_src_ipc_unittests",
-    ":perfetto_src_perfetto_cmd_perfetto_atoms",
+    ":perfetto_src_kallsyms_kallsyms",
+    ":perfetto_src_kallsyms_unittests",
     ":perfetto_src_perfetto_cmd_perfetto_cmd",
     ":perfetto_src_perfetto_cmd_protos_gen",
     ":perfetto_src_perfetto_cmd_trigger_producer",
@@ -7847,6 +8565,7 @@
     ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_interning_output",
     ":perfetto_src_profiling_common_proc_utils",
+    ":perfetto_src_profiling_common_profiler_guardrails",
     ":perfetto_src_profiling_common_unittests",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_deobfuscator",
@@ -7879,6 +8598,7 @@
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
     ":perfetto_src_trace_processor_importers_common",
+    ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
     ":perfetto_src_trace_processor_importers_unittests",
     ":perfetto_src_trace_processor_lib",
     ":perfetto_src_trace_processor_metatrace",
@@ -7909,10 +8629,9 @@
     ":perfetto_src_traced_probes_data_source",
     ":perfetto_src_traced_probes_filesystem_filesystem",
     ":perfetto_src_traced_probes_filesystem_unittests",
-    ":perfetto_src_traced_probes_ftrace_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_format_parser",
+    ":perfetto_src_traced_probes_ftrace_format_parser_unittests",
     ":perfetto_src_traced_probes_ftrace_ftrace",
-    ":perfetto_src_traced_probes_ftrace_kallsyms_kallsyms",
-    ":perfetto_src_traced_probes_ftrace_kallsyms_unittests",
     ":perfetto_src_traced_probes_ftrace_test_messages_cpp_gen",
     ":perfetto_src_traced_probes_ftrace_test_messages_lite_gen",
     ":perfetto_src_traced_probes_ftrace_test_messages_zero_gen",
@@ -7922,6 +8641,7 @@
     ":perfetto_src_traced_probes_initial_display_state_unittests",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_packages_list_unittests",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
@@ -7949,11 +8669,14 @@
     ":perfetto_tools_sanitizers_unittests_sanitizers_unittests",
   ],
   shared_libs: [
+    "libandroidicu",
     "libbase",
     "liblog",
     "libprocinfo",
     "libprotobuf-cpp-lite",
+    "libsqlite",
     "libunwindstack",
+    "libutils",
     "libz",
   ],
   static_libs: [
@@ -7964,7 +8687,6 @@
     "perfetto_gtest_logcat_printer",
   ],
   generated_headers: [
-    "gen_merged_sql_metrics",
     "perfetto_protos_perfetto_common_cpp_gen_headers",
     "perfetto_protos_perfetto_common_zero_gen_headers",
     "perfetto_protos_perfetto_config_android_cpp_gen_headers",
@@ -7976,6 +8698,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -8022,12 +8746,17 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
     "perfetto_src_ipc_test_messages_cpp_gen_headers",
     "perfetto_src_ipc_test_messages_ipc_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
     "perfetto_src_protozero_testing_messages_cpp_gen_headers",
     "perfetto_src_protozero_testing_messages_lite_gen_headers",
     "perfetto_src_protozero_testing_messages_zero_gen_headers",
+    "perfetto_src_trace_processor_importers_gen_cc_config_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_merged_sql_metrics",
     "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen_headers",
     "perfetto_src_traced_probes_ftrace_test_messages_lite_gen_headers",
     "perfetto_src_traced_probes_ftrace_test_messages_zero_gen_headers",
@@ -8043,25 +8772,13 @@
   include_dirs: [
     "bionic/libc/kernel",
   ],
+  header_libs: [
+    "bionic_libc_platform_headers",
+  ],
   data: [
     "src/traced/probes/filesystem/testdata/**/*",
     "src/traced/probes/ftrace/test/data/**/*",
   ],
-  target: {
-    android: {
-      shared_libs: [
-        "libandroidicu",
-        "liblog",
-        "libsqlite",
-        "libutils",
-      ],
-    },
-    host: {
-      static_libs: [
-        "libsqlite",
-      ],
-    },
-  },
 }
 
 // GN: //src/protozero/protoc_plugin:protozero_plugin
@@ -8076,6 +8793,9 @@
   static_libs: [
     "libprotoc",
   ],
+  generated_headers: [
+    "perfetto_src_base_version_gen_h",
+  ],
   defaults: [
     "perfetto_defaults",
   ],
@@ -8086,13 +8806,15 @@
 }
 
 // GN: //src/trace_processor:trace_processor_shell
-cc_binary_host {
+cc_binary {
   name: "trace_processor_shell",
   srcs: [
     ":perfetto_include_perfetto_base_base",
     ":perfetto_include_perfetto_ext_base_base",
     ":perfetto_include_perfetto_ext_trace_processor_export_json",
+    ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
     ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
+    ":perfetto_include_perfetto_profiling_deobfuscator",
     ":perfetto_include_perfetto_protozero_protozero",
     ":perfetto_include_perfetto_trace_processor_basic_types",
     ":perfetto_include_perfetto_trace_processor_storage",
@@ -8102,6 +8824,7 @@
     ":perfetto_protos_perfetto_config_ftrace_zero_gen",
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_zero_gen",
     ":perfetto_protos_perfetto_config_profiling_zero_gen",
@@ -8119,12 +8842,14 @@
     ":perfetto_protos_perfetto_trace_perfetto_zero_gen",
     ":perfetto_protos_perfetto_trace_power_zero_gen",
     ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
+    ":perfetto_protos_perfetto_trace_processor_zero_gen",
     ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_base_base",
+    ":perfetto_src_profiling_deobfuscator",
     ":perfetto_src_profiling_symbolizer_symbolize_database",
     ":perfetto_src_profiling_symbolizer_symbolizer",
     ":perfetto_src_protozero_protozero",
@@ -8134,6 +8859,7 @@
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
     ":perfetto_src_trace_processor_importers_common",
+    ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
     ":perfetto_src_trace_processor_lib",
     ":perfetto_src_trace_processor_metatrace",
     ":perfetto_src_trace_processor_metrics_lib",
@@ -8145,22 +8871,19 @@
     ":perfetto_src_trace_processor_track_event_descriptor",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_protozero_to_text",
     ":perfetto_src_trace_processor_util_util",
     "src/trace_processor/trace_processor_shell.cc",
     "src/trace_processor/util/proto_to_json.cc",
   ],
-  static_libs: [
-    "libprotoc",
-    "libsqlite",
-    "libz",
-  ],
+  host_supported: true,
   generated_headers: [
-    "gen_merged_sql_metrics",
     "perfetto_protos_perfetto_common_zero_gen_headers",
     "perfetto_protos_perfetto_config_android_zero_gen_headers",
     "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
@@ -8178,11 +8901,17 @@
     "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
     "perfetto_protos_perfetto_trace_power_zero_gen_headers",
     "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
     "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
+    "perfetto_src_trace_processor_importers_gen_cc_config_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_merged_sql_metrics",
   ],
   defaults: [
     "perfetto_defaults",
@@ -8192,7 +8921,34 @@
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
     "-DHAVE_HIDDEN",
   ],
-  stl: "libc++_static",
+  target: {
+    android: {
+      shared_libs: [
+        "libandroidicu",
+        "liblog",
+        "libprotobuf-cpp-full",
+        "libsqlite",
+        "libutils",
+        "libz",
+      ],
+    },
+    host: {
+      static_libs: [
+        "libprotobuf-cpp-full",
+        "libsqlite",
+        "libz",
+      ],
+      stl: "libc++_static",
+      dist: {
+        targets: [
+          "sdk_repo",
+        ],
+      },
+      strip: {
+        all: true,
+      },
+    },
+  },
 }
 
 // GN: //tools/trace_to_text:trace_to_text
@@ -8202,6 +8958,7 @@
     ":perfetto_include_perfetto_base_base",
     ":perfetto_include_perfetto_ext_base_base",
     ":perfetto_include_perfetto_ext_trace_processor_export_json",
+    ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
     ":perfetto_include_perfetto_ext_traced_sys_stats_counters",
     ":perfetto_include_perfetto_profiling_deobfuscator",
     ":perfetto_include_perfetto_profiling_pprof_builder",
@@ -8214,6 +8971,7 @@
     ":perfetto_protos_perfetto_config_ftrace_zero_gen",
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_zero_gen",
     ":perfetto_protos_perfetto_config_profiling_zero_gen",
@@ -8231,6 +8989,7 @@
     ":perfetto_protos_perfetto_trace_perfetto_zero_gen",
     ":perfetto_protos_perfetto_trace_power_zero_gen",
     ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
+    ":perfetto_protos_perfetto_trace_processor_zero_gen",
     ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
@@ -8248,6 +9007,7 @@
     ":perfetto_src_trace_processor_export_json",
     ":perfetto_src_trace_processor_ftrace_descriptors",
     ":perfetto_src_trace_processor_importers_common",
+    ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor",
     ":perfetto_src_trace_processor_lib",
     ":perfetto_src_trace_processor_metatrace",
     ":perfetto_src_trace_processor_metrics_lib",
@@ -8259,26 +9019,25 @@
     ":perfetto_src_trace_processor_track_event_descriptor",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_protozero_to_text",
     ":perfetto_src_trace_processor_util_util",
     ":perfetto_tools_trace_to_text_common",
     ":perfetto_tools_trace_to_text_full",
     ":perfetto_tools_trace_to_text_pprofbuilder",
     ":perfetto_tools_trace_to_text_utils",
   ],
-  shared_libs: [
-    "libprotobuf-cpp-full",
-  ],
   static_libs: [
+    "libprotobuf-cpp-full",
     "libsqlite",
     "libz",
   ],
   generated_headers: [
-    "gen_merged_sql_metrics",
     "perfetto_protos_perfetto_common_zero_gen_headers",
     "perfetto_protos_perfetto_config_android_zero_gen_headers",
     "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
@@ -8296,12 +9055,18 @@
     "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
     "perfetto_protos_perfetto_trace_power_zero_gen_headers",
     "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
     "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_third_party_pprof_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
+    "perfetto_src_trace_processor_importers_gen_cc_config_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
+    "perfetto_src_trace_processor_metrics_gen_merged_sql_metrics",
   ],
   defaults: [
     "perfetto_defaults",
@@ -8357,6 +9122,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -8391,10 +9158,12 @@
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
     ":perfetto_src_ipc_common",
+    ":perfetto_src_kallsyms_kallsyms",
     ":perfetto_src_profiling_common_callstack_trie",
     ":perfetto_src_profiling_common_interner",
     ":perfetto_src_profiling_common_interning_output",
     ":perfetto_src_profiling_common_proc_utils",
+    ":perfetto_src_profiling_common_profiler_guardrails",
     ":perfetto_src_profiling_common_unwind_support",
     ":perfetto_src_profiling_perf_common_types",
     ":perfetto_src_profiling_perf_proc_descriptors",
@@ -8431,6 +9200,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -8461,6 +9232,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
   ],
   defaults: [
     "perfetto_defaults",
@@ -8520,6 +9292,8 @@
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
+    ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_zero_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
@@ -8550,6 +9324,10 @@
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
+    ":perfetto_src_android_internal_headers",
+    ":perfetto_src_android_internal_lazy_library_loader",
+    ":perfetto_src_android_stats_android_stats",
+    ":perfetto_src_android_stats_perfetto_atoms",
     ":perfetto_src_base_base",
     ":perfetto_src_base_unix_socket",
     ":perfetto_src_ipc_client",
@@ -8579,6 +9357,8 @@
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
@@ -8609,6 +9389,7 @@
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_src_base_version_gen_h",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
   defaults: [
@@ -8700,29 +9481,3 @@
         "statslog_perfetto.cpp",
     ],
 }
-
-cc_genrule {
-  name: "trace_processor_shell.stripped",
-  device_supported: false,
-  host_supported: true,
-  cmd: "$(location tools/strip_android_host_binary.py) $(in) -o $(out)",
-  enabled: false,
-  compile_multilib: "64",
-  tool_files: [
-    "tools/strip_android_host_binary.py",
-  ],
-  dist: {
-    targets: [
-      "sdk_repo",
-    ],
-  },
-  target: {
-    linux: {
-      out: [
-        "trace_processor_shell.stripped",
-      ],
-      srcs: [":trace_processor_shell"],
-      enabled: true,
-    },
-  },
-}
\ No newline at end of file
diff --git a/Android.bp.extras b/Android.bp.extras
index a4615eb..65a81f4 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -78,29 +78,3 @@
         "statslog_perfetto.cpp",
     ],
 }
-
-cc_genrule {
-  name: "trace_processor_shell.stripped",
-  device_supported: false,
-  host_supported: true,
-  cmd: "$(location tools/strip_android_host_binary.py) $(in) -o $(out)",
-  enabled: false,
-  compile_multilib: "64",
-  tool_files: [
-    "tools/strip_android_host_binary.py",
-  ],
-  dist: {
-    targets: [
-      "sdk_repo",
-    ],
-  },
-  target: {
-    linux: {
-      out: [
-        "trace_processor_shell.stripped",
-      ],
-      srcs: [":trace_processor_shell"],
-      enabled: true,
-    },
-  },
-}
diff --git a/BUILD b/BUILD
index bba653d..e4e4cdb 100644
--- a/BUILD
+++ b/BUILD
@@ -20,12 +20,14 @@
     "perfetto_cc_binary",
     "perfetto_cc_ipc_library",
     "perfetto_cc_library",
+    "perfetto_cc_proto_descriptor",
     "perfetto_cc_proto_library",
     "perfetto_cc_protocpp_library",
     "perfetto_cc_protozero_library",
     "perfetto_java_proto_library",
     "perfetto_java_lite_proto_library",
     "perfetto_proto_library",
+    "perfetto_proto_descriptor",
     "perfetto_py_binary",
     "perfetto_py_library",
     "perfetto_gensignature_internal_only",
@@ -46,11 +48,9 @@
     name = "ipc_plugin",
     srcs = [
         "src/ipc/protoc_plugin/ipc_plugin.cc",
-        ":include_perfetto_base_base",
-        ":include_perfetto_ext_base_base",
-        ":src_base_base",
     ],
     deps = [
+        ":src_base_base",
     ] + PERFETTO_CONFIG.deps.protoc_lib,
 )
 
@@ -58,8 +58,6 @@
 perfetto_cc_library(
     name = "perfetto_ipc",
     srcs = [
-        ":src_base_base",
-        ":src_base_unix_socket",
         ":src_ipc_client",
         ":src_ipc_common",
         ":src_ipc_host",
@@ -72,6 +70,8 @@
     ],
     deps = [
         ":protos_perfetto_ipc_wire_protocol_cpp",
+        ":src_base_base",
+        ":src_base_unix_socket",
     ],
     linkstatic = True,
 )
@@ -81,11 +81,9 @@
     name = "cppgen_plugin",
     srcs = [
         "src/protozero/protoc_plugin/cppgen_plugin.cc",
-        ":include_perfetto_base_base",
-        ":include_perfetto_ext_base_base",
-        ":src_base_base",
     ],
     deps = [
+        ":src_base_base",
     ] + PERFETTO_CONFIG.deps.protoc_lib,
 )
 
@@ -94,26 +92,35 @@
     name = "protozero_plugin",
     srcs = [
         "src/protozero/protoc_plugin/protozero_plugin.cc",
-        ":include_perfetto_base_base",
-        ":include_perfetto_ext_base_base",
-        ":src_base_base",
     ],
     deps = [
+        ":src_base_base",
     ] + PERFETTO_CONFIG.deps.protoc_lib,
 )
 
-# GN target: //src/protozero:libprotozero
+# GN target: //src/protozero:protozero
 perfetto_cc_library(
-    name = "libprotozero",
+    name = "protozero",
     srcs = [
-        ":src_base_base",
-        ":src_protozero_protozero",
+        "src/protozero/field.cc",
+        "src/protozero/message.cc",
+        "src/protozero/message_arena.cc",
+        "src/protozero/message_handle.cc",
+        "src/protozero/packed_repeated_fields.cc",
+        "src/protozero/proto_decoder.cc",
+        "src/protozero/scattered_heap_buffer.cc",
+        "src/protozero/scattered_stream_null_delegate.cc",
+        "src/protozero/scattered_stream_writer.cc",
+        "src/protozero/static_buffer.cc",
+        "src/protozero/virtual_destructors.cc",
     ],
     hdrs = [
         ":include_perfetto_base_base",
-        ":include_perfetto_ext_base_base",
         ":include_perfetto_protozero_protozero",
     ],
+    deps = [
+        ":src_base_base",
+    ],
     linkstatic = True,
 )
 
@@ -135,6 +142,7 @@
         ":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",
@@ -167,22 +175,18 @@
     srcs = [
         ":src_android_internal_headers",
         ":src_android_internal_lazy_library_loader",
-        ":src_base_base",
-        ":src_base_unix_socket",
-        ":src_ipc_client",
-        ":src_ipc_common",
-        ":src_ipc_host",
-        ":src_perfetto_cmd_perfetto_atoms",
-        ":src_protozero_protozero",
+        ":src_android_stats_perfetto_atoms",
+        ":src_kallsyms_kallsyms",
         ":src_traced_probes_android_log_android_log",
         ":src_traced_probes_common_common",
         ":src_traced_probes_data_source",
         ":src_traced_probes_filesystem_filesystem",
-        ":src_traced_probes_ftrace_format_parser",
+        ":src_traced_probes_ftrace_format_parser_format_parser",
         ":src_traced_probes_ftrace_ftrace",
         ":src_traced_probes_initial_display_state_initial_display_state",
         ":src_traced_probes_metatrace_metatrace",
         ":src_traced_probes_packages_list_packages_list",
+        ":src_traced_probes_packages_list_packages_list_parser",
         ":src_traced_probes_power_power",
         ":src_traced_probes_probes",
         ":src_traced_probes_probes_src",
@@ -214,6 +218,7 @@
         ":include_perfetto_tracing_tracing",
     ],
     deps = [
+        ":perfetto_ipc",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_cpp",
@@ -225,6 +230,8 @@
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_cpp",
         ":protos_perfetto_config_inode_file_zero",
+        ":protos_perfetto_config_interceptors_cpp",
+        ":protos_perfetto_config_interceptors_zero",
         ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_config_power_zero",
         ":protos_perfetto_config_process_stats_cpp",
@@ -238,7 +245,6 @@
         ":protos_perfetto_config_zero",
         ":protos_perfetto_ipc_cpp",
         ":protos_perfetto_ipc_ipc",
-        ":protos_perfetto_ipc_wire_protocol_cpp",
         ":protos_perfetto_trace_android_zero",
         ":protos_perfetto_trace_chrome_zero",
         ":protos_perfetto_trace_filesystem_zero",
@@ -255,6 +261,8 @@
         ":protos_perfetto_trace_system_info_zero",
         ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
+        ":protozero",
+        ":src_base_base",
     ],
     linkstatic = True,
 )
@@ -268,7 +276,9 @@
         "include/perfetto/base/export.h",
         "include/perfetto/base/flat_set.h",
         "include/perfetto/base/logging.h",
+        "include/perfetto/base/platform_handle.h",
         "include/perfetto/base/proc_utils.h",
+        "include/perfetto/base/status.h",
         "include/perfetto/base/task_runner.h",
         "include/perfetto/base/thread_utils.h",
         "include/perfetto/base/time.h",
@@ -281,10 +291,10 @@
     srcs = [
         "include/perfetto/ext/base/circular_queue.h",
         "include/perfetto/ext/base/container_annotations.h",
+        "include/perfetto/ext/base/endian.h",
         "include/perfetto/ext/base/event_fd.h",
         "include/perfetto/ext/base/file_utils.h",
         "include/perfetto/ext/base/hash.h",
-        "include/perfetto/ext/base/lookup_set.h",
         "include/perfetto/ext/base/metatrace.h",
         "include/perfetto/ext/base/metatrace_events.h",
         "include/perfetto/ext/base/no_destructor.h",
@@ -307,6 +317,7 @@
         "include/perfetto/ext/base/unix_task_runner.h",
         "include/perfetto/ext/base/utils.h",
         "include/perfetto/ext/base/uuid.h",
+        "include/perfetto/ext/base/version.h",
         "include/perfetto/ext/base/waitable_event.h",
         "include/perfetto/ext/base/watchdog.h",
         "include/perfetto/ext/base/watchdog_noop.h",
@@ -332,6 +343,19 @@
     ],
 )
 
+# GN target: //include/perfetto/ext/trace_processor/importers/memory_tracker:memory_tracker
+filegroup(
+    name = "include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
+    srcs = [
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/graph.h",
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h",
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h",
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h",
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h",
+        "include/perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h",
+    ],
+)
+
 # GN target: //include/perfetto/ext/trace_processor:export_json
 filegroup(
     name = "include_perfetto_ext_trace_processor_export_json",
@@ -413,10 +437,12 @@
         "include/perfetto/protozero/cpp_message_obj.h",
         "include/perfetto/protozero/field.h",
         "include/perfetto/protozero/message.h",
+        "include/perfetto/protozero/message_arena.h",
         "include/perfetto/protozero/message_handle.h",
         "include/perfetto/protozero/packed_repeated_fields.h",
         "include/perfetto/protozero/proto_decoder.h",
         "include/perfetto/protozero/proto_utils.h",
+        "include/perfetto/protozero/root_message.h",
         "include/perfetto/protozero/scattered_heap_buffer.h",
         "include/perfetto/protozero/scattered_stream_null_delegate.h",
         "include/perfetto/protozero/scattered_stream_writer.h",
@@ -485,12 +511,15 @@
     name = "include_perfetto_tracing_tracing",
     srcs = [
         "include/perfetto/tracing/buffer_exhausted_policy.h",
+        "include/perfetto/tracing/console_interceptor.h",
         "include/perfetto/tracing/data_source.h",
         "include/perfetto/tracing/debug_annotation.h",
         "include/perfetto/tracing/event_context.h",
+        "include/perfetto/tracing/interceptor.h",
         "include/perfetto/tracing/internal/basic_types.h",
         "include/perfetto/tracing/internal/data_source_internal.h",
         "include/perfetto/tracing/internal/in_process_tracing_backend.h",
+        "include/perfetto/tracing/internal/interceptor_trace_writer.h",
         "include/perfetto/tracing/internal/system_tracing_backend.h",
         "include/perfetto/tracing/internal/tracing_muxer.h",
         "include/perfetto/tracing/internal/tracing_tls.h",
@@ -507,6 +536,7 @@
         "include/perfetto/tracing/track_event_category_registry.h",
         "include/perfetto/tracing/track_event_interned_data_index.h",
         "include/perfetto/tracing/track_event_legacy.h",
+        "include/perfetto/tracing/track_event_state_tracker.h",
     ],
 )
 
@@ -515,7 +545,6 @@
     name = "src_android_internal_headers",
     srcs = [
         "src/android_internal/atrace_hal.h",
-        "src/android_internal/dropbox_service.h",
         "src/android_internal/health_hal.h",
         "src/android_internal/incident_service.h",
         "src/android_internal/power_stats_hal.h",
@@ -532,8 +561,25 @@
     ],
 )
 
-# GN target: //src/base:base
+# GN target: //src/android_stats:android_stats
 filegroup(
+    name = "src_android_stats_android_stats",
+    srcs = [
+        "src/android_stats/statsd_logging_helper.cc",
+        "src/android_stats/statsd_logging_helper.h",
+    ],
+)
+
+# GN target: //src/android_stats:perfetto_atoms
+filegroup(
+    name = "src_android_stats_perfetto_atoms",
+    srcs = [
+        "src/android_stats/perfetto_atoms.h",
+    ],
+)
+
+# GN target: //src/base:base
+perfetto_cc_library(
     name = "src_base_base",
     srcs = [
         "src/base/event_fd.cc",
@@ -542,28 +588,57 @@
         "src/base/metatrace.cc",
         "src/base/paged_memory.cc",
         "src/base/pipe.cc",
+        "src/base/status.cc",
         "src/base/string_splitter.cc",
         "src/base/string_utils.cc",
         "src/base/string_view.cc",
-        "src/base/subprocess.cc",
+        "src/base/subprocess_posix.cc",
         "src/base/temp_file.cc",
         "src/base/thread_checker.cc",
         "src/base/thread_task_runner.cc",
         "src/base/time.cc",
         "src/base/unix_task_runner.cc",
+        "src/base/utils.cc",
         "src/base/uuid.cc",
+        "src/base/version.cc",
         "src/base/virtual_destructors.cc",
         "src/base/waitable_event.cc",
         "src/base/watchdog_posix.cc",
     ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+    ],
+    deps = [
+    ] + PERFETTO_CONFIG.deps.version_header,
+    linkstatic = True,
 )
 
 # GN target: //src/base:unix_socket
-filegroup(
+perfetto_cc_library(
     name = "src_base_unix_socket",
     srcs = [
         "src/base/unix_socket.cc",
     ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+    ],
+    linkstatic = True,
+)
+
+genrule(
+    name = "src_base_version_gen_h",
+    srcs = [
+        "CHANGELOG",
+    ],
+    outs = [
+        "perfetto_version.gen.h",
+    ],
+    cmd = "$(location gen_version_header_py) --cpp_out=$@ --changelog=$(location CHANGELOG)",
+    exec_tools = [
+        ":gen_version_header_py",
+    ],
 )
 
 # GN target: //src/ipc:client
@@ -596,11 +671,14 @@
     ],
 )
 
-# GN target: //src/perfetto_cmd:perfetto_atoms
+# GN target: //src/kallsyms:kallsyms
 filegroup(
-    name = "src_perfetto_cmd_perfetto_atoms",
+    name = "src_kallsyms_kallsyms",
     srcs = [
-        "src/perfetto_cmd/perfetto_atoms.h",
+        "src/kallsyms/kernel_symbol_map.cc",
+        "src/kallsyms/kernel_symbol_map.h",
+        "src/kallsyms/lazy_kernel_symbolizer.cc",
+        "src/kallsyms/lazy_kernel_symbolizer.h",
     ],
 )
 
@@ -644,8 +722,17 @@
 filegroup(
     name = "src_profiling_symbolizer_symbolizer",
     srcs = [
+        "src/profiling/symbolizer/filesystem.h",
+        "src/profiling/symbolizer/filesystem_posix.cc",
+        "src/profiling/symbolizer/filesystem_windows.cc",
         "src/profiling/symbolizer/local_symbolizer.cc",
         "src/profiling/symbolizer/local_symbolizer.h",
+        "src/profiling/symbolizer/scoped_read_mmap.h",
+        "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
+        "src/profiling/symbolizer/scoped_read_mmap_windows.cc",
+        "src/profiling/symbolizer/subprocess.h",
+        "src/profiling/symbolizer/subprocess_posix.cc",
+        "src/profiling/symbolizer/subprocess_windows.cc",
         "src/profiling/symbolizer/symbolizer.cc",
         "src/profiling/symbolizer/symbolizer.h",
     ],
@@ -659,23 +746,6 @@
     ],
 )
 
-# GN target: //src/protozero:protozero
-filegroup(
-    name = "src_protozero_protozero",
-    srcs = [
-        "src/protozero/field.cc",
-        "src/protozero/message.cc",
-        "src/protozero/message_handle.cc",
-        "src/protozero/packed_repeated_fields.cc",
-        "src/protozero/proto_decoder.cc",
-        "src/protozero/scattered_heap_buffer.cc",
-        "src/protozero/scattered_stream_null_delegate.cc",
-        "src/protozero/scattered_stream_writer.cc",
-        "src/protozero/static_buffer.cc",
-        "src/protozero/virtual_destructors.cc",
-    ],
-)
-
 # GN target: //src/trace_processor/analysis:analysis
 filegroup(
     name = "src_trace_processor_analysis_analysis",
@@ -717,6 +787,18 @@
     ],
 )
 
+# GN target: //src/trace_processor/importers/memory_tracker:graph_processor
+filegroup(
+    name = "src_trace_processor_importers_memory_tracker_graph_processor",
+    srcs = [
+        "src/trace_processor/importers/memory_tracker/graph.cc",
+        "src/trace_processor/importers/memory_tracker/graph_processor.cc",
+        "src/trace_processor/importers/memory_tracker/memory_allocator_node_id.cc",
+        "src/trace_processor/importers/memory_tracker/raw_memory_graph_node.cc",
+        "src/trace_processor/importers/memory_tracker/raw_process_memory_node.cc",
+    ],
+)
+
 # GN target: //src/trace_processor/importers:common
 filegroup(
     name = "src_trace_processor_importers_common",
@@ -727,6 +809,8 @@
         "src/trace_processor/importers/common/clock_tracker.h",
         "src/trace_processor/importers/common/event_tracker.cc",
         "src/trace_processor/importers/common/event_tracker.h",
+        "src/trace_processor/importers/common/flow_tracker.cc",
+        "src/trace_processor/importers/common/flow_tracker.h",
         "src/trace_processor/importers/common/global_args_tracker.cc",
         "src/trace_processor/importers/common/global_args_tracker.h",
         "src/trace_processor/importers/common/process_tracker.cc",
@@ -740,6 +824,36 @@
     ],
 )
 
+perfetto_cc_proto_descriptor(
+    name = "src_trace_processor_importers_gen_cc_config_descriptor",
+    deps = [
+        ":protos_perfetto_config_descriptor",
+    ],
+    outs = [
+        "src/trace_processor/importers/config.descriptor.h",
+    ],
+)
+
+perfetto_cc_proto_descriptor(
+    name = "src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+    deps = [
+        ":protos_perfetto_metrics_chrome_descriptor",
+    ],
+    outs = [
+        "src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h",
+    ],
+)
+
+perfetto_cc_proto_descriptor(
+    name = "src_trace_processor_metrics_gen_cc_metrics_descriptor",
+    deps = [
+        ":protos_perfetto_metrics_descriptor",
+    ],
+    outs = [
+        "src/trace_processor/metrics/metrics.descriptor.h",
+    ],
+)
+
 genrule(
     name = "src_trace_processor_metrics_gen_merged_sql_metrics",
     srcs = [
@@ -747,6 +861,7 @@
         "src/trace_processor/metrics/android/android_cpu.sql",
         "src/trace_processor/metrics/android/android_cpu_agg.sql",
         "src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql",
+        "src/trace_processor/metrics/android/android_gpu.sql",
         "src/trace_processor/metrics/android/android_hwui_metric.sql",
         "src/trace_processor/metrics/android/android_ion.sql",
         "src/trace_processor/metrics/android/android_lmk.sql",
@@ -759,18 +874,20 @@
         "src/trace_processor/metrics/android/android_startup.sql",
         "src/trace_processor/metrics/android/android_startup_launches.sql",
         "src/trace_processor/metrics/android/android_surfaceflinger.sql",
+        "src/trace_processor/metrics/android/android_sysui_cuj.sql",
         "src/trace_processor/metrics/android/android_task_names.sql",
         "src/trace_processor/metrics/android/android_task_state.sql",
         "src/trace_processor/metrics/android/android_thread_time_in_state.sql",
-        "src/trace_processor/metrics/android/counter_span_view.sql",
         "src/trace_processor/metrics/android/cpu_info.sql",
         "src/trace_processor/metrics/android/display_metrics.sql",
         "src/trace_processor/metrics/android/frame_missed.sql",
+        "src/trace_processor/metrics/android/global_counter_span_view.sql",
         "src/trace_processor/metrics/android/heap_profile_callsites.sql",
         "src/trace_processor/metrics/android/hsc_startups.sql",
         "src/trace_processor/metrics/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/android/java_heap_stats.sql",
         "src/trace_processor/metrics/android/mem_stats_priority_breakdown.sql",
+        "src/trace_processor/metrics/android/power_drain_in_watts.sql",
         "src/trace_processor/metrics/android/power_profile_data.sql",
         "src/trace_processor/metrics/android/process_counter_span_view.sql",
         "src/trace_processor/metrics/android/process_mem.sql",
@@ -778,16 +895,26 @@
         "src/trace_processor/metrics/android/process_oom_score.sql",
         "src/trace_processor/metrics/android/process_unagg_mem_view.sql",
         "src/trace_processor/metrics/android/span_view_stats.sql",
-        "src/trace_processor/metrics/android/unmapped_java_symbols.sql",
         "src/trace_processor/metrics/android/unsymbolized_frames.sql",
+        "src/trace_processor/metrics/chrome/actual_power_by_category.sql",
+        "src/trace_processor/metrics/chrome/actual_power_by_rail_mode.sql",
+        "src/trace_processor/metrics/chrome/chrome_event_metadata.sql",
         "src/trace_processor/metrics/chrome/chrome_processes.sql",
-        "src/trace_processor/metrics/chrome/console_error_metric.sql",
+        "src/trace_processor/metrics/chrome/chrome_thread_slice_with_cpu_time.sql",
+        "src/trace_processor/metrics/chrome/cpu_time_by_category.sql",
+        "src/trace_processor/metrics/chrome/cpu_time_by_rail_mode.sql",
+        "src/trace_processor/metrics/chrome/estimated_power_by_category.sql",
+        "src/trace_processor/metrics/chrome/estimated_power_by_rail_mode.sql",
+        "src/trace_processor/metrics/chrome/rail_modes.sql",
         "src/trace_processor/metrics/chrome/scroll_flow_event.sql",
         "src/trace_processor/metrics/chrome/scroll_flow_event_queuing_delay.sql",
         "src/trace_processor/metrics/chrome/scroll_jank.sql",
         "src/trace_processor/metrics/chrome/scroll_jank_cause.sql",
         "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_task.sql",
         "src/trace_processor/metrics/chrome/scroll_jank_cause_blocking_touch_move.sql",
+        "src/trace_processor/metrics/chrome/scroll_jank_cause_get_bitmap.sql",
+        "src/trace_processor/metrics/chrome/scroll_jank_cause_queuing_delay.sql",
+        "src/trace_processor/metrics/chrome/test_chrome_metric.sql",
         "src/trace_processor/metrics/trace_metadata.sql",
         "src/trace_processor/metrics/webview/webview_power_usage.sql",
     ],
@@ -795,7 +922,7 @@
         "src/trace_processor/metrics/sql_metrics.h",
     ],
     cmd = "$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)",
-    tools = [
+    exec_tools = [
         ":gen_merged_sql_metrics_py",
     ],
 )
@@ -804,9 +931,7 @@
 filegroup(
     name = "src_trace_processor_metrics_lib",
     srcs = [
-        "src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h",
         "src/trace_processor/metrics/metrics.cc",
-        "src/trace_processor/metrics/metrics.descriptor.h",
         "src/trace_processor/metrics/metrics.h",
     ],
 )
@@ -876,8 +1001,10 @@
     srcs = [
         "src/trace_processor/tables/android_tables.h",
         "src/trace_processor/tables/counter_tables.h",
+        "src/trace_processor/tables/flow_tables.h",
         "src/trace_processor/tables/macros.h",
         "src/trace_processor/tables/macros_internal.h",
+        "src/trace_processor/tables/memory_tables.h",
         "src/trace_processor/tables/metadata_tables.h",
         "src/trace_processor/tables/profiler_tables.h",
         "src/trace_processor/tables/slice_tables.h",
@@ -913,6 +1040,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/util:protozero_to_text
+filegroup(
+    name = "src_trace_processor_util_protozero_to_text",
+    srcs = [
+        "src/trace_processor/util/protozero_to_text.cc",
+        "src/trace_processor/util/protozero_to_text.h",
+    ],
+)
+
 # GN target: //src/trace_processor/util:util
 filegroup(
     name = "src_trace_processor_util_util",
@@ -943,8 +1079,10 @@
 filegroup(
     name = "src_trace_processor_lib",
     srcs = [
-        "src/trace_processor/dynamic/ancestor_slice_generator.cc",
-        "src/trace_processor/dynamic/ancestor_slice_generator.h",
+        "src/trace_processor/dynamic/ancestor_generator.cc",
+        "src/trace_processor/dynamic/ancestor_generator.h",
+        "src/trace_processor/dynamic/connected_flow_generator.cc",
+        "src/trace_processor/dynamic/connected_flow_generator.h",
         "src/trace_processor/dynamic/descendant_slice_generator.cc",
         "src/trace_processor/dynamic/descendant_slice_generator.h",
         "src/trace_processor/dynamic/describe_slice_generator.cc",
@@ -953,8 +1091,12 @@
         "src/trace_processor/dynamic/experimental_counter_dur_generator.h",
         "src/trace_processor/dynamic/experimental_flamegraph_generator.cc",
         "src/trace_processor/dynamic/experimental_flamegraph_generator.h",
+        "src/trace_processor/dynamic/experimental_sched_upid_generator.cc",
+        "src/trace_processor/dynamic/experimental_sched_upid_generator.h",
         "src/trace_processor/dynamic/experimental_slice_layout_generator.cc",
         "src/trace_processor/dynamic/experimental_slice_layout_generator.h",
+        "src/trace_processor/dynamic/thread_state_generator.cc",
+        "src/trace_processor/dynamic/thread_state_generator.h",
         "src/trace_processor/iterator_impl.cc",
         "src/trace_processor/iterator_impl.h",
         "src/trace_processor/read_trace.cc",
@@ -1065,8 +1207,14 @@
         "src/trace_processor/importers/ninja/ninja_log_parser.h",
         "src/trace_processor/importers/proto/args_table_utils.cc",
         "src/trace_processor/importers/proto/args_table_utils.h",
+        "src/trace_processor/importers/proto/async_track_set_tracker.cc",
+        "src/trace_processor/importers/proto/async_track_set_tracker.h",
         "src/trace_processor/importers/proto/heap_profile_tracker.cc",
         "src/trace_processor/importers/proto/heap_profile_tracker.h",
+        "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
+        "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h",
+        "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
+        "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h",
         "src/trace_processor/importers/proto/metadata_tracker.cc",
         "src/trace_processor/importers/proto/metadata_tracker.h",
         "src/trace_processor/importers/proto/packet_sequence_state.cc",
@@ -1075,11 +1223,15 @@
         "src/trace_processor/importers/proto/profile_module.h",
         "src/trace_processor/importers/proto/profile_packet_utils.cc",
         "src/trace_processor/importers/proto/profile_packet_utils.h",
+        "src/trace_processor/importers/proto/profiler_util.cc",
+        "src/trace_processor/importers/proto/profiler_util.h",
         "src/trace_processor/importers/proto/proto_importer_module.cc",
         "src/trace_processor/importers/proto/proto_importer_module.h",
         "src/trace_processor/importers/proto/proto_incremental_state.h",
         "src/trace_processor/importers/proto/proto_trace_parser.cc",
         "src/trace_processor/importers/proto/proto_trace_parser.h",
+        "src/trace_processor/importers/proto/proto_trace_reader.cc",
+        "src/trace_processor/importers/proto/proto_trace_reader.h",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.h",
         "src/trace_processor/importers/proto/stack_profile_tracker.cc",
@@ -1090,6 +1242,8 @@
         "src/trace_processor/importers/proto/track_event_parser.h",
         "src/trace_processor/importers/proto/track_event_tokenizer.cc",
         "src/trace_processor/importers/proto/track_event_tokenizer.h",
+        "src/trace_processor/importers/proto/track_event_tracker.cc",
+        "src/trace_processor/importers/proto/track_event_tracker.h",
         "src/trace_processor/importers/syscalls/syscall_tracker.h",
         "src/trace_processor/importers/systrace/systrace_line.h",
         "src/trace_processor/timestamped_trace_piece.h",
@@ -1150,12 +1304,12 @@
     ],
 )
 
-# GN target: //src/traced/probes/ftrace:format_parser
+# GN target: //src/traced/probes/ftrace/format_parser:format_parser
 filegroup(
-    name = "src_traced_probes_ftrace_format_parser",
+    name = "src_traced_probes_ftrace_format_parser_format_parser",
     srcs = [
-        "src/traced/probes/ftrace/format_parser.cc",
-        "src/traced/probes/ftrace/format_parser.h",
+        "src/traced/probes/ftrace/format_parser/format_parser.cc",
+        "src/traced/probes/ftrace/format_parser/format_parser.h",
     ],
 )
 
@@ -1192,6 +1346,8 @@
         "src/traced/probes/ftrace/ftrace_procfs.h",
         "src/traced/probes/ftrace/ftrace_stats.cc",
         "src/traced/probes/ftrace/ftrace_stats.h",
+        "src/traced/probes/ftrace/printk_formats_parser.cc",
+        "src/traced/probes/ftrace/printk_formats_parser.h",
         "src/traced/probes/ftrace/proto_translation_table.cc",
         "src/traced/probes/ftrace/proto_translation_table.h",
     ],
@@ -1224,6 +1380,15 @@
     ],
 )
 
+# GN target: //src/traced/probes/packages_list:packages_list_parser
+filegroup(
+    name = "src_traced_probes_packages_list_packages_list_parser",
+    srcs = [
+        "src/traced/probes/packages_list/packages_list_parser.cc",
+        "src/traced/probes/packages_list/packages_list_parser.h",
+    ],
+)
+
 # GN target: //src/traced/probes/power:power
 filegroup(
     name = "src_traced_probes_power_power",
@@ -1385,9 +1550,12 @@
 filegroup(
     name = "src_tracing_client_api_without_backends",
     srcs = [
+        "src/tracing/console_interceptor.cc",
         "src/tracing/data_source.cc",
         "src/tracing/debug_annotation.cc",
         "src/tracing/event_context.cc",
+        "src/tracing/interceptor.cc",
+        "src/tracing/internal/interceptor_trace_writer.cc",
         "src/tracing/internal/tracing_muxer_impl.cc",
         "src/tracing/internal/tracing_muxer_impl.h",
         "src/tracing/internal/track_event_internal.cc",
@@ -1396,6 +1564,7 @@
         "src/tracing/track.cc",
         "src/tracing/track_event_category_registry.cc",
         "src/tracing/track_event_legacy.cc",
+        "src/tracing/track_event_state_tracker.cc",
         "src/tracing/virtual_destructors.cc",
     ],
 )
@@ -1441,6 +1610,8 @@
         "tools/trace_to_text/main.cc",
         "tools/trace_to_text/symbolize_profile.cc",
         "tools/trace_to_text/symbolize_profile.h",
+        "tools/trace_to_text/trace_to_hprof.cc",
+        "tools/trace_to_text/trace_to_hprof.h",
         "tools/trace_to_text/trace_to_json.cc",
         "tools/trace_to_text/trace_to_json.h",
         "tools/trace_to_text/trace_to_profile.cc",
@@ -1508,6 +1679,7 @@
         "protos/perfetto/common/data_source_descriptor.proto",
         "protos/perfetto/common/descriptor.proto",
         "protos/perfetto/common/gpu_counter_descriptor.proto",
+        "protos/perfetto/common/interceptor_descriptor.proto",
         "protos/perfetto/common/observable_events.proto",
         "protos/perfetto/common/sys_stats_counters.proto",
         "protos/perfetto/common/trace_stats.proto",
@@ -1581,12 +1753,24 @@
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_profiling_cpp",
         ":protos_perfetto_config_gpu_cpp",
+        ":protos_perfetto_config_interceptors_cpp",
         ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_sys_stats_cpp",
     ],
 )
 
+# GN target: //protos/perfetto/config:descriptor
+perfetto_proto_descriptor(
+    name = "protos_perfetto_config_descriptor",
+    deps = [
+        ":protos_perfetto_config_protos",
+    ],
+    outs = [
+        "protos_perfetto_config_descriptor.bin",
+    ],
+)
+
 # GN target: //protos/perfetto/config/ftrace:cpp
 perfetto_cc_protocpp_library(
     name = "protos_perfetto_config_ftrace_cpp",
@@ -1693,6 +1877,45 @@
     ],
 )
 
+# GN target: //protos/perfetto/config/interceptors:cpp
+perfetto_cc_protocpp_library(
+    name = "protos_perfetto_config_interceptors_cpp",
+    deps = [
+        ":protos_perfetto_config_interceptors_protos",
+        ":protos_perfetto_common_cpp",
+    ],
+)
+
+# GN target: //protos/perfetto/config/interceptors:lite
+perfetto_cc_proto_library(
+    name = "protos_perfetto_config_interceptors_lite",
+    deps = [
+        ":protos_perfetto_config_interceptors_protos",
+    ],
+)
+
+# GN target: //protos/perfetto/config/interceptors:zero
+perfetto_proto_library(
+    name = "protos_perfetto_config_interceptors_protos",
+    srcs = [
+        "protos/perfetto/config/interceptors/console_config.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+    deps = [
+        ":protos_perfetto_common_protos",
+    ],
+)
+
+# GN target: //protos/perfetto/config/interceptors:zero
+perfetto_cc_protozero_library(
+    name = "protos_perfetto_config_interceptors_zero",
+    deps = [
+        ":protos_perfetto_config_interceptors_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/config:lite
 perfetto_cc_proto_library(
     name = "protos_perfetto_config_lite",
@@ -1831,6 +2054,8 @@
     srcs = [
         "protos/perfetto/config/chrome/chrome_config.proto",
         "protos/perfetto/config/data_source_config.proto",
+        "protos/perfetto/config/interceptor_config.proto",
+        "protos/perfetto/config/stress_test_config.proto",
         "protos/perfetto/config/test_config.proto",
         "protos/perfetto/config/trace_config.proto",
     ],
@@ -1841,6 +2066,7 @@
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
+        ":protos_perfetto_config_interceptors_protos",
         ":protos_perfetto_config_power_protos",
         ":protos_perfetto_config_process_stats_protos",
         ":protos_perfetto_config_profiling_protos",
@@ -1943,6 +2169,7 @@
         ":protos_perfetto_config_ftrace_cpp",
         ":protos_perfetto_config_profiling_cpp",
         ":protos_perfetto_config_gpu_cpp",
+        ":protos_perfetto_config_interceptors_cpp",
         ":protos_perfetto_config_cpp",
         ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_common_cpp",
@@ -1957,7 +2184,7 @@
         ":protos_perfetto_ipc_protos",
         ":protos_perfetto_config_android_cpp",
         ":protos_perfetto_config_track_event_cpp",
-        ":protos_perfetto_ipc_wire_protocol_cpp",
+        ":protos_perfetto_config_interceptors_cpp",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_config_process_stats_cpp",
         ":protos_perfetto_config_ftrace_cpp",
@@ -1987,13 +2214,13 @@
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
+        ":protos_perfetto_config_interceptors_protos",
         ":protos_perfetto_config_power_protos",
         ":protos_perfetto_config_process_stats_protos",
         ":protos_perfetto_config_profiling_protos",
         ":protos_perfetto_config_protos",
         ":protos_perfetto_config_sys_stats_protos",
         ":protos_perfetto_config_track_event_protos",
-        ":protos_perfetto_ipc_wire_protocol_protos",
     ],
 )
 
@@ -2024,13 +2251,14 @@
     ],
 )
 
-# GN target: //protos/perfetto/metrics/android:lite
+# GN target: //protos/perfetto/metrics/android:source_set
 perfetto_proto_library(
     name = "protos_perfetto_metrics_android_protos",
     srcs = [
         "protos/perfetto/metrics/android/batt_metric.proto",
         "protos/perfetto/metrics/android/cpu_metric.proto",
         "protos/perfetto/metrics/android/display_metrics.proto",
+        "protos/perfetto/metrics/android/gpu_metric.proto",
         "protos/perfetto/metrics/android/heap_profile_callsites.proto",
         "protos/perfetto/metrics/android/hwui_metric.proto",
         "protos/perfetto/metrics/android/ion_metric.proto",
@@ -2045,14 +2273,66 @@
         "protos/perfetto/metrics/android/process_metadata.proto",
         "protos/perfetto/metrics/android/startup_metric.proto",
         "protos/perfetto/metrics/android/surfaceflinger.proto",
+        "protos/perfetto/metrics/android/sysui_cuj_metrics.proto",
         "protos/perfetto/metrics/android/task_names.proto",
         "protos/perfetto/metrics/android/thread_time_in_state_metric.proto",
-        "protos/perfetto/metrics/android/unmapped_java_symbols.proto",
         "protos/perfetto/metrics/android/unsymbolized_frames.proto",
     ],
     visibility = PERFETTO_CONFIG.public_visibility,
 )
 
+# GN target: //protos/perfetto/metrics/chrome:descriptor
+perfetto_proto_descriptor(
+    name = "protos_perfetto_metrics_chrome_descriptor",
+    deps = [
+        ":protos_perfetto_metrics_chrome_protos",
+    ],
+    outs = [
+        "protos_perfetto_metrics_chrome_descriptor.bin",
+    ],
+)
+
+# GN target: //protos/perfetto/metrics/chrome:source_set
+perfetto_proto_library(
+    name = "protos_perfetto_metrics_chrome_protos",
+    srcs = [
+        "protos/perfetto/metrics/chrome/all_chrome_metrics.proto",
+        "protos/perfetto/metrics/chrome/test_chrome_metric.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+    deps = [
+        ":protos_perfetto_metrics_android_protos",
+        ":protos_perfetto_metrics_custom_options_protos",
+        ":protos_perfetto_metrics_protos",
+    ] + PERFETTO_CONFIG.deps.protobuf_descriptor_proto,
+)
+
+# GN target: //protos/perfetto/metrics:custom_options_source_set
+perfetto_proto_library(
+    name = "protos_perfetto_metrics_custom_options_protos",
+    srcs = [
+        "protos/perfetto/metrics/custom_options.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+    deps = [
+    ] + PERFETTO_CONFIG.deps.protobuf_descriptor_proto,
+)
+
+# GN target: //protos/perfetto/metrics:descriptor
+perfetto_proto_descriptor(
+    name = "protos_perfetto_metrics_descriptor",
+    deps = [
+        ":protos_perfetto_metrics_protos",
+    ],
+    outs = [
+        "protos_perfetto_metrics_descriptor.bin",
+    ],
+)
+
 # GN target: //protos/perfetto/metrics:lite
 perfetto_cc_proto_library(
     name = "protos_perfetto_metrics_lite",
@@ -2061,7 +2341,7 @@
     ],
 )
 
-# GN target: //protos/perfetto/metrics:lite
+# GN target: //protos/perfetto/metrics:source_set
 perfetto_proto_library(
     name = "protos_perfetto_metrics_protos",
     srcs = [
@@ -2086,6 +2366,7 @@
     name = "protos_perfetto_trace_android_protos",
     srcs = [
         "protos/perfetto/trace/android/android_log.proto",
+        "protos/perfetto/trace/android/frame_timeline_event.proto",
         "protos/perfetto/trace/android/gpu_mem_event.proto",
         "protos/perfetto/trace/android/graphics_frame_event.proto",
         "protos/perfetto/trace/android/initial_display_state.proto",
@@ -2180,14 +2461,18 @@
         "protos/perfetto/trace/ftrace/cgroup.proto",
         "protos/perfetto/trace/ftrace/clk.proto",
         "protos/perfetto/trace/ftrace/compaction.proto",
+        "protos/perfetto/trace/ftrace/cpuhp.proto",
+        "protos/perfetto/trace/ftrace/dpu.proto",
         "protos/perfetto/trace/ftrace/ext4.proto",
         "protos/perfetto/trace/ftrace/f2fs.proto",
+        "protos/perfetto/trace/ftrace/fastrpc.proto",
         "protos/perfetto/trace/ftrace/fence.proto",
         "protos/perfetto/trace/ftrace/filemap.proto",
         "protos/perfetto/trace/ftrace/ftrace.proto",
         "protos/perfetto/trace/ftrace/ftrace_event.proto",
         "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
         "protos/perfetto/trace/ftrace/ftrace_stats.proto",
+        "protos/perfetto/trace/ftrace/g2d.proto",
         "protos/perfetto/trace/ftrace/generic.proto",
         "protos/perfetto/trace/ftrace/gpu_mem.proto",
         "protos/perfetto/trace/ftrace/i2c.proto",
@@ -2336,6 +2621,7 @@
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
+        ":protos_perfetto_config_interceptors_protos",
         ":protos_perfetto_config_power_protos",
         ":protos_perfetto_config_process_stats_protos",
         ":protos_perfetto_config_profiling_protos",
@@ -2366,6 +2652,7 @@
     name = "protos_perfetto_trace_non_minimal_protos",
     srcs = [
         "protos/perfetto/trace/extension_descriptor.proto",
+        "protos/perfetto/trace/memory_graph.proto",
         "protos/perfetto/trace/test_event.proto",
         "protos/perfetto/trace/trace.proto",
         "protos/perfetto/trace/trace_packet.proto",
@@ -2378,6 +2665,7 @@
         ":protos_perfetto_config_ftrace_protos",
         ":protos_perfetto_config_gpu_protos",
         ":protos_perfetto_config_inode_file_protos",
+        ":protos_perfetto_config_interceptors_protos",
         ":protos_perfetto_config_power_protos",
         ":protos_perfetto_config_process_stats_protos",
         ":protos_perfetto_config_profiling_protos",
@@ -2493,6 +2781,9 @@
     visibility = [
         PERFETTO_CONFIG.proto_library_visibility,
     ],
+    deps = [
+        ":protos_perfetto_common_protos",
+    ],
 )
 
 # GN target: //protos/perfetto/trace_processor:zero
@@ -2515,6 +2806,7 @@
 perfetto_proto_library(
     name = "protos_perfetto_trace_profiling_protos",
     srcs = [
+        "protos/perfetto/trace/profiling/deobfuscation.proto",
         "protos/perfetto/trace/profiling/heap_graph.proto",
         "protos/perfetto/trace/profiling/profile_common.proto",
         "protos/perfetto/trace/profiling/profile_packet.proto",
@@ -2638,15 +2930,20 @@
 perfetto_proto_library(
     name = "protos_perfetto_trace_track_event_protos",
     srcs = [
+        "protos/perfetto/trace/track_event/chrome_application_state_info.proto",
         "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto",
         "protos/perfetto/trace/track_event/chrome_frame_reporter.proto",
         "protos/perfetto/trace/track_event/chrome_histogram_sample.proto",
         "protos/perfetto/trace/track_event/chrome_keyed_service.proto",
         "protos/perfetto/trace/track_event/chrome_latency_info.proto",
         "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto",
+        "protos/perfetto/trace/track_event/chrome_message_pump.proto",
+        "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto",
         "protos/perfetto/trace/track_event/chrome_process_descriptor.proto",
+        "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto",
         "protos/perfetto/trace/track_event/chrome_thread_descriptor.proto",
         "protos/perfetto/trace/track_event/chrome_user_event.proto",
+        "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto",
         "protos/perfetto/trace/track_event/counter_descriptor.proto",
         "protos/perfetto/trace/track_event/debug_annotation.proto",
         "protos/perfetto/trace/track_event/log_message.proto",
@@ -2716,12 +3013,6 @@
 perfetto_cc_library(
     name = "libperfetto_client_experimental",
     srcs = [
-        ":src_base_base",
-        ":src_base_unix_socket",
-        ":src_ipc_client",
-        ":src_ipc_common",
-        ":src_ipc_host",
-        ":src_protozero_protozero",
         ":src_tracing_client_api_without_backends",
         ":src_tracing_common",
         ":src_tracing_core_core",
@@ -2750,6 +3041,7 @@
         "//visibility:public",
     ],
     deps = [
+        ":perfetto_ipc",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_cpp",
@@ -2761,6 +3053,8 @@
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_cpp",
         ":protos_perfetto_config_inode_file_zero",
+        ":protos_perfetto_config_interceptors_cpp",
+        ":protos_perfetto_config_interceptors_zero",
         ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_config_power_zero",
         ":protos_perfetto_config_process_stats_cpp",
@@ -2774,7 +3068,6 @@
         ":protos_perfetto_config_zero",
         ":protos_perfetto_ipc_cpp",
         ":protos_perfetto_ipc_ipc",
-        ":protos_perfetto_ipc_wire_protocol_cpp",
         ":protos_perfetto_trace_android_zero",
         ":protos_perfetto_trace_chrome_zero",
         ":protos_perfetto_trace_filesystem_zero",
@@ -2791,6 +3084,8 @@
         ":protos_perfetto_trace_system_info_zero",
         ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
+        ":protozero",
+        ":src_base_base",
     ],
     linkstatic = True,
 )
@@ -2812,14 +3107,10 @@
         ":include_perfetto_tracing_tracing",
         ":src_android_internal_headers",
         ":src_android_internal_lazy_library_loader",
-        ":src_base_base",
-        ":src_base_unix_socket",
-        ":src_ipc_client",
-        ":src_ipc_common",
-        ":src_perfetto_cmd_perfetto_atoms",
+        ":src_android_stats_android_stats",
+        ":src_android_stats_perfetto_atoms",
         ":src_perfetto_cmd_perfetto_cmd",
         ":src_perfetto_cmd_trigger_producer",
-        ":src_protozero_protozero",
         ":src_tracing_common",
         ":src_tracing_core_core",
         ":src_tracing_ipc_common",
@@ -2830,6 +3121,7 @@
         "//visibility:public",
     ],
     deps = [
+        ":perfetto_ipc",
         ":protos_perfetto_common_cpp",
         ":protos_perfetto_common_zero",
         ":protos_perfetto_config_android_cpp",
@@ -2841,6 +3133,8 @@
         ":protos_perfetto_config_gpu_zero",
         ":protos_perfetto_config_inode_file_cpp",
         ":protos_perfetto_config_inode_file_zero",
+        ":protos_perfetto_config_interceptors_cpp",
+        ":protos_perfetto_config_interceptors_zero",
         ":protos_perfetto_config_power_cpp",
         ":protos_perfetto_config_power_zero",
         ":protos_perfetto_config_process_stats_cpp",
@@ -2854,7 +3148,6 @@
         ":protos_perfetto_config_zero",
         ":protos_perfetto_ipc_cpp",
         ":protos_perfetto_ipc_ipc",
-        ":protos_perfetto_ipc_wire_protocol_cpp",
         ":protos_perfetto_trace_android_zero",
         ":protos_perfetto_trace_chrome_zero",
         ":protos_perfetto_trace_filesystem_zero",
@@ -2871,6 +3164,8 @@
         ":protos_perfetto_trace_system_info_zero",
         ":protos_perfetto_trace_track_event_cpp",
         ":protos_perfetto_trace_track_event_zero",
+        ":protozero",
+        ":src_base_base",
         ":src_perfetto_cmd_protos",
     ] + PERFETTO_CONFIG.deps.zlib,
 )
@@ -2879,14 +3174,13 @@
 perfetto_cc_library(
     name = "trace_processor",
     srcs = [
-        ":src_base_base",
-        ":src_protozero_protozero",
         ":src_trace_processor_analysis_analysis",
         ":src_trace_processor_containers_containers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_export_json",
         ":src_trace_processor_ftrace_descriptors",
         ":src_trace_processor_importers_common",
+        ":src_trace_processor_importers_memory_tracker_graph_processor",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_lib",
@@ -2898,12 +3192,14 @@
         ":src_trace_processor_track_event_descriptor",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_util",
     ],
     hdrs = [
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
         ":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_trace_processor_basic_types",
@@ -2919,87 +3215,7 @@
                ":protos_perfetto_config_ftrace_zero",
                ":protos_perfetto_config_gpu_zero",
                ":protos_perfetto_config_inode_file_zero",
-               ":protos_perfetto_config_power_zero",
-               ":protos_perfetto_config_process_stats_zero",
-               ":protos_perfetto_config_profiling_zero",
-               ":protos_perfetto_config_sys_stats_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_metrics_impl_zero",
-               ":protos_perfetto_trace_profiling_zero",
-               ":protos_perfetto_trace_ps_zero",
-               ":protos_perfetto_trace_sys_stats_zero",
-               ":protos_perfetto_trace_system_info_zero",
-               ":protos_perfetto_trace_track_event_zero",
-           ] + PERFETTO_CONFIG.deps.jsoncpp +
-           PERFETTO_CONFIG.deps.sqlite +
-           PERFETTO_CONFIG.deps.sqlite_ext_percentile +
-           PERFETTO_CONFIG.deps.zlib + [
-        ":cc_merged_sql_metrics",
-    ],
-    linkstatic = True,
-)
-
-# GN target: //src/trace_processor:trace_processor_shell
-perfetto_cc_binary(
-    name = "trace_processor_shell",
-    srcs = [
-        "src/trace_processor/trace_processor_shell.cc",
-        "src/trace_processor/util/proto_to_json.cc",
-        "src/trace_processor/util/proto_to_json.h",
-        ":include_perfetto_base_base",
-        ":include_perfetto_ext_base_base",
-        ":include_perfetto_ext_trace_processor_export_json",
-        ":include_perfetto_ext_traced_sys_stats_counters",
-        ":include_perfetto_protozero_protozero",
-        ":include_perfetto_trace_processor_basic_types",
-        ":include_perfetto_trace_processor_storage",
-        ":include_perfetto_trace_processor_trace_processor",
-        ":src_base_base",
-        ":src_base_unix_socket",
-        ":src_profiling_symbolizer_symbolize_database",
-        ":src_profiling_symbolizer_symbolizer",
-        ":src_protozero_protozero",
-        ":src_trace_processor_analysis_analysis",
-        ":src_trace_processor_containers_containers",
-        ":src_trace_processor_db_lib",
-        ":src_trace_processor_export_json",
-        ":src_trace_processor_ftrace_descriptors",
-        ":src_trace_processor_importers_common",
-        ":src_trace_processor_lib",
-        ":src_trace_processor_metatrace",
-        ":src_trace_processor_metrics_lib",
-        ":src_trace_processor_rpc_httpd",
-        ":src_trace_processor_rpc_rpc",
-        ":src_trace_processor_sqlite_sqlite",
-        ":src_trace_processor_storage_full",
-        ":src_trace_processor_storage_minimal",
-        ":src_trace_processor_storage_storage",
-        ":src_trace_processor_tables_tables",
-        ":src_trace_processor_track_event_descriptor",
-        ":src_trace_processor_types_types",
-        ":src_trace_processor_util_descriptors",
-        ":src_trace_processor_util_util",
-    ],
-    visibility = [
-        "//visibility:public",
-    ],
-    deps = [
-               ":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",
@@ -3023,9 +3239,105 @@
                ":protos_perfetto_trace_sys_stats_zero",
                ":protos_perfetto_trace_system_info_zero",
                ":protos_perfetto_trace_track_event_zero",
+               ":protozero",
+               ":src_base_base",
+               ":src_trace_processor_importers_gen_cc_config_descriptor",
+               ":src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+               ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
+           ] + PERFETTO_CONFIG.deps.jsoncpp +
+           PERFETTO_CONFIG.deps.sqlite +
+           PERFETTO_CONFIG.deps.sqlite_ext_percentile +
+           PERFETTO_CONFIG.deps.zlib + [
+        ":cc_merged_sql_metrics",
+    ],
+    linkstatic = True,
+)
+
+# GN target: //src/trace_processor:trace_processor_shell
+perfetto_cc_binary(
+    name = "trace_processor_shell",
+    srcs = [
+        "src/trace_processor/trace_processor_shell.cc",
+        "src/trace_processor/util/proto_to_json.cc",
+        "src/trace_processor/util/proto_to_json.h",
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+        ":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_profiling_deobfuscator",
+        ":include_perfetto_protozero_protozero",
+        ":include_perfetto_trace_processor_basic_types",
+        ":include_perfetto_trace_processor_storage",
+        ":include_perfetto_trace_processor_trace_processor",
+        ":src_profiling_deobfuscator",
+        ":src_profiling_symbolizer_symbolize_database",
+        ":src_profiling_symbolizer_symbolizer",
+        ":src_trace_processor_analysis_analysis",
+        ":src_trace_processor_containers_containers",
+        ":src_trace_processor_db_lib",
+        ":src_trace_processor_export_json",
+        ":src_trace_processor_ftrace_descriptors",
+        ":src_trace_processor_importers_common",
+        ":src_trace_processor_importers_memory_tracker_graph_processor",
+        ":src_trace_processor_lib",
+        ":src_trace_processor_metatrace",
+        ":src_trace_processor_metrics_lib",
+        ":src_trace_processor_rpc_httpd",
+        ":src_trace_processor_rpc_rpc",
+        ":src_trace_processor_sqlite_sqlite",
+        ":src_trace_processor_storage_full",
+        ":src_trace_processor_storage_minimal",
+        ":src_trace_processor_storage_storage",
+        ":src_trace_processor_tables_tables",
+        ":src_trace_processor_track_event_descriptor",
+        ":src_trace_processor_types_types",
+        ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
+        ":src_trace_processor_util_util",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+    deps = [
+               ":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_sys_stats_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_metrics_impl_zero",
+               ":protos_perfetto_trace_processor_zero",
+               ":protos_perfetto_trace_profiling_zero",
+               ":protos_perfetto_trace_ps_zero",
+               ":protos_perfetto_trace_sys_stats_zero",
+               ":protos_perfetto_trace_system_info_zero",
+               ":protos_perfetto_trace_track_event_zero",
+               ":protozero",
+               ":src_base_base",
+               ":src_base_unix_socket",
+               ":src_trace_processor_importers_gen_cc_config_descriptor",
+               ":src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+               ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
            ] + PERFETTO_CONFIG.deps.jsoncpp +
            PERFETTO_CONFIG.deps.linenoise +
-           PERFETTO_CONFIG.deps.protoc_lib +
+           PERFETTO_CONFIG.deps.protobuf_full +
            PERFETTO_CONFIG.deps.sqlite +
            PERFETTO_CONFIG.deps.sqlite_ext_percentile +
            PERFETTO_CONFIG.deps.zlib + [
@@ -3092,6 +3404,7 @@
         ":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",
@@ -3114,6 +3427,7 @@
         ":protos_perfetto_trace_system_info_zero",
         ":protos_perfetto_trace_track_event_zero",
         ":protos_third_party_pprof_zero",
+        ":protozero",
     ] + PERFETTO_CONFIG.deps.zlib,
     linkstatic = True,
 )
@@ -3125,6 +3439,7 @@
         ":include_perfetto_base_base",
         ":include_perfetto_ext_base_base",
         ":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_profiling_deobfuscator",
         ":include_perfetto_profiling_pprof_builder",
@@ -3132,17 +3447,16 @@
         ":include_perfetto_trace_processor_basic_types",
         ":include_perfetto_trace_processor_storage",
         ":include_perfetto_trace_processor_trace_processor",
-        ":src_base_base",
         ":src_profiling_deobfuscator",
         ":src_profiling_symbolizer_symbolize_database",
         ":src_profiling_symbolizer_symbolizer",
-        ":src_protozero_protozero",
         ":src_trace_processor_analysis_analysis",
         ":src_trace_processor_containers_containers",
         ":src_trace_processor_db_lib",
         ":src_trace_processor_export_json",
         ":src_trace_processor_ftrace_descriptors",
         ":src_trace_processor_importers_common",
+        ":src_trace_processor_importers_memory_tracker_graph_processor",
         ":src_trace_processor_lib",
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_lib",
@@ -3154,6 +3468,7 @@
         ":src_trace_processor_track_event_descriptor",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_util",
         ":tools_trace_to_text_common",
         ":tools_trace_to_text_full",
@@ -3169,6 +3484,7 @@
                ":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",
@@ -3186,12 +3502,18 @@
                ":protos_perfetto_trace_perfetto_zero",
                ":protos_perfetto_trace_power_zero",
                ":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_sys_stats_zero",
                ":protos_perfetto_trace_system_info_zero",
                ":protos_perfetto_trace_track_event_zero",
                ":protos_third_party_pprof_zero",
+               ":protozero",
+               ":src_base_base",
+               ":src_trace_processor_importers_gen_cc_config_descriptor",
+               ":src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+               ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
            ] + PERFETTO_CONFIG.deps.jsoncpp +
            PERFETTO_CONFIG.deps.protobuf_full +
            PERFETTO_CONFIG.deps.sqlite +
@@ -3227,6 +3549,15 @@
     python_version = "PY3",
 )
 
+perfetto_py_binary(
+    name = "gen_cc_proto_descriptor_py",
+    srcs = [
+        "tools/gen_cc_proto_descriptor.py",
+    ],
+    main = "tools/gen_cc_proto_descriptor.py",
+    python_version = "PY3",
+)
+
 perfetto_java_proto_library(
     name = "protos_perfetto_metrics_java",
     deps = [
@@ -3274,6 +3605,21 @@
     ],
 )
 
+# This is overridden in google internal builds via
+# PERFETTO_CONFIG.deps.version_header (see perfetto_cfg.bzl).
+perfetto_cc_library(
+    name = "cc_perfetto_version_header",
+    hdrs = ["perfetto_version.gen.h"],
+)
+
+perfetto_py_binary(
+    name = "gen_version_header_py",
+    srcs = ["tools/write_version_header.py"],
+    data = ["CHANGELOG"],
+    main = "tools/write_version_header.py",
+    python_version = "PY3",
+)
+
 # Noop targets used to represent targets of the protobuf library.
 # These will be rewritten in Google3 to be dependencies on the real targets.
 
@@ -3292,6 +3638,11 @@
     srcs = [],
 )
 
+perfetto_py_library(
+    name = "trace_processor_init_noop",
+    srcs = [],
+)
+
 perfetto_py_binary(
     name = "trace_processor_py_example",
     srcs = ["src/trace_processor/python/example.py"],
@@ -3302,10 +3653,11 @@
 
 perfetto_py_library(
     name = "trace_processor_py",
-    srcs = glob(['src/trace_processor/python/trace_processor/*.py']),
+    srcs = glob(["src/trace_processor/python/perfetto/trace_processor/*.py"]),
     data = [
-        "src/trace_processor/python/trace_processor/trace_processor.descriptor",
-        "src/trace_processor/python/trace_processor/metrics.descriptor",
+        ":trace_processor_init_noop",
+        "src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor",
+        "src/trace_processor/python/perfetto/trace_processor/metrics.descriptor",
         ":trace_processor_shell",
     ],
     deps = [
diff --git a/BUILD.extras b/BUILD.extras
index 825120a..65611ef 100644
--- a/BUILD.extras
+++ b/BUILD.extras
@@ -22,6 +22,15 @@
     python_version = "PY3",
 )
 
+perfetto_py_binary(
+    name = "gen_cc_proto_descriptor_py",
+    srcs = [
+        "tools/gen_cc_proto_descriptor.py",
+    ],
+    main = "tools/gen_cc_proto_descriptor.py",
+    python_version = "PY3",
+)
+
 perfetto_java_proto_library(
     name = "protos_perfetto_metrics_java",
     deps = [
@@ -69,6 +78,21 @@
     ],
 )
 
+# This is overridden in google internal builds via
+# PERFETTO_CONFIG.deps.version_header (see perfetto_cfg.bzl).
+perfetto_cc_library(
+    name = "cc_perfetto_version_header",
+    hdrs = ["perfetto_version.gen.h"],
+)
+
+perfetto_py_binary(
+    name = "gen_version_header_py",
+    srcs = ["tools/write_version_header.py"],
+    data = ["CHANGELOG"],
+    main = "tools/write_version_header.py",
+    python_version = "PY3",
+)
+
 # Noop targets used to represent targets of the protobuf library.
 # These will be rewritten in Google3 to be dependencies on the real targets.
 
@@ -87,6 +111,11 @@
     srcs = [],
 )
 
+perfetto_py_library(
+    name = "trace_processor_init_noop",
+    srcs = [],
+)
+
 perfetto_py_binary(
     name = "trace_processor_py_example",
     srcs = ["src/trace_processor/python/example.py"],
@@ -97,10 +126,11 @@
 
 perfetto_py_library(
     name = "trace_processor_py",
-    srcs = glob(['src/trace_processor/python/trace_processor/*.py']),
+    srcs = glob(["src/trace_processor/python/perfetto/trace_processor/*.py"]),
     data = [
-        "src/trace_processor/python/trace_processor/trace_processor.descriptor",
-        "src/trace_processor/python/trace_processor/metrics.descriptor",
+        ":trace_processor_init_noop",
+        "src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor",
+        "src/trace_processor/python/perfetto/trace_processor/metrics.descriptor",
         ":trace_processor_shell",
     ],
     deps = [
diff --git a/BUILD.gn b/BUILD.gn
index 0a1b23c..b5fbddd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -44,7 +44,10 @@
 }
 
 if (enable_perfetto_heapprofd) {
-  all_targets += [ "src/profiling/memory:heapprofd" ]
+  all_targets += [
+    "src/profiling/memory:heapprofd",
+    "src/profiling/memory:heapprofd_preload",
+  ]
   if (perfetto_build_with_android) {
     all_targets += [
       "src/profiling/memory:heapprofd_client",
@@ -81,6 +84,7 @@
   all_targets += [
     ":perfetto_integrationtests",
     "test:client_api_example",
+    "test/stress_test",
   ]
 }
 
@@ -117,7 +121,6 @@
 # compile-time checks for the CI.
 if (perfetto_build_standalone) {
   all_targets += [
-    "src/tracing/consumer_api_deprecated:consumer_api_test",
     "test/configs",
 
     # For syntax-checking the proto.
@@ -139,6 +142,9 @@
     # Checks that the "fake" backend implementations build.
     "src/tracing:client_api_no_backends_compile_test",
   ]
+  if (is_linux || is_android) {
+    all_targets += [ "src/tracing/consumer_api_deprecated:consumer_api_test" ]
+  }
 }
 
 # The CTS code is built (but not ran) also in standalone builds. This is to
@@ -234,6 +240,7 @@
   component("libperfetto") {
     public_configs = [ "gn:public_config" ]
     deps = [
+      "src/trace_processor/importers/memory_tracker:graph_processor",
       "src/tracing:client_api",
       "src/tracing/core",
 
@@ -243,6 +250,7 @@
     configs -= [ "//build/config/compiler:chromium_code" ]
     configs += [ "//build/config/compiler:no_chromium_code" ]
     public_deps = [
+      "include/perfetto/ext/trace_processor/importers/memory_tracker",
       "include/perfetto/ext/tracing/core",
       "include/perfetto/tracing",
       "protos/perfetto/common:zero",
@@ -250,6 +258,7 @@
       "protos/perfetto/trace/chrome:zero",
       "protos/perfetto/trace/interned_data:zero",
       "protos/perfetto/trace/profiling:zero",
+      "protos/perfetto/trace/ps:zero",
       "protos/perfetto/trace/track_event:zero",
     ]
     if (enable_perfetto_ipc) {
diff --git a/CHANGELOG b/CHANGELOG
index 9f58771..65d16b4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,141 @@
 Unreleased:
   Tracing service and probes:
-    *
+    * Added trace packet interceptor API for rerouting trace data into
+      non-Perfetto systems.
+    * Added support for printing track events to the console.
+    * Added a way to observe track event tracing sessions starting and
+      stopping.
   Trace Processor:
-    * Local Symbolizer allows to use PERFETTO_SYMBOLIZER_MODE=index to
-      discover symbol files by build id rather than name.
+    *
   UI:
     *
 
 
+v10.0 - 2020-12-01:
+  Tracing service and probes:
+    * Fixed crash of tracing service if a client is unresponsive on the IPC
+      channel. Clients are disconnected if they don't respond to IPCs for 10s.
+    * Added cmdline arguments for integration within ChromeOS system image
+      (--{producer,consumer}-socket-{group,mode} for chmod-ing sockets).
+    * Changed path lookup logic for traced socket. /run/perfetto/ is used if the
+      directory exists, falling back on /tmp/ otherwise.
+    * Added support for kernel frame symbolization to the traced_perf callstack
+      sampler.
+    * Added support for resolving ftrace event arguments that require
+      symbolization against printk_format.
+  Trace Processor:
+    * Added .read command to inject a SQL query file, similar to the -q cmdline.
+    * Added trace-based metrics to root cause jank in Android System UI.
+    * Added symbolization support for ELF files on Windows for heap and
+      callstack profilers.
+    * Added support for symbolizing names of workqueue ftrace events.
+    * Improved Android startup metric with activity restart time.
+  UI:
+    * Added support for navigating flows with Ctrl+[ / Ctr+].
+    * Improved query result panel, moved to the bottom group allowing
+      simultaneous query result and timeline views.
+    * Fixed data corruption when recording traces via the WebUSB-based Record
+      page in the UI.
+
+
+v9.0 - 2020-11-01:
+  Tracing service and probes:
+    * Added support for recording traces from a system service through the
+      client API.
+    * The client library now reconnects producers automatically if the
+      connection to the tracing service is lost. Also fixed crashes in ongoing
+      tracing sessions when a disconnect occurs.
+    * Added support for dpu and g2d ftrace events.
+    * Enabled commit batching and producer side patching of chunks.
+    * Add support for symbolizing kernel symbols for ftrace events.
+  Trace Processor:
+    * Fixed type affinity of string columns.
+  UI:
+    * Added initial support for running metrics from the UI.
+    * Added support for displaying all flows when a slice or area is selected.
+    * Highlight nodes that match the 'focus' string in the flamegraph.
+    * Added search within slice args.
+    * Fixed details panel height and moved query panel into details panel.
+    * Enabled re-sharing of postMessage traces by echoing back the original URL.
+    * Improved record page error messages.
+
+
+v8.0 - 2020-10-01:
+  Tracing service and probes:
+    * Added API for querying registered data sources and their capabilities.
+    * Added support for profiling heap allocations on Linux via LD_PRELOAD.
+    * Fixed possible race when initializing the consumer library.
+    * Fixed subtle bugs on systems with 16KB system pages.
+  Trace Processor:
+    * Added a table which lists available metrics.
+    * Added Python bindings on PyPi in the 'perfetto' package.
+    * Added support for running trace_processor_shell on Android.
+    * Added per-process metrics for GPU memory usage.
+    * Added support for exporting flow events to JSON.
+    * Added dynamic tables for navigating between slices of flows.
+  UI:
+    * Changed time marking: horizontal selection doesn't gray out anymore,
+      pressing 'm' marks the range.
+    * Added initial support for displaying flow event arrows.
+    * Improved ordering of all thread tracks under process grouping.
+    * Fixed UI crashes due to metric errors
+    * Fixed selection of thread state slices.
+
+
+v7.0 - 2020-09-01:
+  Tracing service and probes:
+    * Added auto-reconnection to the SDK. Tracing::Initialize() now retries in
+      the background, instead of failing, if the tracing service is unrechable.
+    * Added support for recording cpuhp (CPU hotplug) ftrace events.
+    * Fixed heap profiling unwinding on multi-ABI systems.
+    * Fixed reporting of live objects in the native heap profiler when using
+      --dump-at-max.
+    * Fixed crash when writing trace events with field nesting level > 10.
+  Trace Processor:
+    * Added Python bindings, see
+      https://perfetto.dev/docs/analysis/trace-processor#python-api .
+    * Added ingestion for Chrome instant events and Chrome flow events.
+    * Added ingestion for Android GPU memory events and sched_blocked_reason.
+    * Added WebView power metric.
+    * Added support for WSL1 where Async I/O is not available.
+    * Improved detection of Android suspend/resume events.
+  UI:
+    * Added GPU memory recording controls and ingestion code. Requires a recent
+      Android 12+ kernel.
+    * Added details panel for flow events, showed when the user selects a slice
+      involved in a flow (arrows in the UI are still being worked on).
+    * Added instant events rendering.
+    * Added Google Analytics.
+    * Fixed I/O thread-states in 4.14 kernels to deal with the removal of
+      wake-kill using sched_blocked_reason.
+    * Fixed "Perfetto UI started debugging this browser" showing when opening
+      the UI and the Chrome extension is installed.
+  Misc:
+    * Update language to comply with Android's inclusive language guidance.
+
+
+v6.0 - 2020-08-01:
+  Tracing service and probes:
+    * Added ftrace thermal events.
+    * Added support for custom allocators to the heap profiler. Allows
+      developers to report memory allocations that are not done through malloc.
+    * Added detailed timestamping of key tracing session events.
+    * Added support for building tracing services on CrOS (system-wide tracing).
+    * Fixed filtering out of stale ftrace data that predates the beginning of
+      the tracing session.
+  Trace Processor:
+    * Improved profile symbolizer. PERFETTO_SYMBOLIZER_MODE=index discovers
+      symbol files by build id rather than name.
+    * Added screen-state Android metrics.
+  UI:
+    * Added 'Info and stats' page to debug data losses and trace stats.
+    * Added full cmdline to process detail panel.
+    * Improved performance of async tracks using quantized queries.
+    * Improved performance of counter and slice tracks for long traces by
+      pre-caching quantized track data.
+    * Improved actionablility of crash dialog when the Wasm module OOMs.
+
+
 v5.0 - 2020-07-01:
   Tracing service and probes:
     * Added gpu_mem_total ftrace event.
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 8378ac1..c090e85 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -22,10 +22,10 @@
   def long_line_sources(x):
     return input.FilterSourceFile(
         x,
-        white_list=".*",
-        black_list=[
+        files_to_check='.*',
+        files_to_skip=[
             'Android[.]bp', '.*[.]json$', '.*[.]sql$', '.*[.]out$',
-            'test/trace_processor/.*/index$', '.*\bBUILD$', 'WORKSPACE',
+            'test/trace_processor/.*/index$', '(.*/)?BUILD$', 'WORKSPACE',
             '.*/Makefile$', '/perfetto_build_flags.h$'
         ])
 
@@ -44,7 +44,7 @@
   results += CheckAndroidBlueprint(input, output)
   results += CheckBinaryDescriptors(input, output)
   results += CheckMergedTraceConfigProto(input, output)
-  results += CheckWhitelist(input, output)
+  results += CheckProtoEventList(input, output)
   results += CheckBannedCpp(input, output)
   return results
 
@@ -63,7 +63,7 @@
   # If no GN files were modified, bail out.
   def build_file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=('.*BUILD[.]gn$', '.*[.]gni$', 'BUILD\.extras', tool))
+        x, files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', 'BUILD\.extras', tool))
 
   if not input_api.AffectedSourceFiles(build_file_filter):
     return []
@@ -81,7 +81,7 @@
   # If no GN files were modified, bail out.
   def build_file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=('.*BUILD[.]gn$', '.*[.]gni$', tool))
+        x, files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', tool))
 
   if not input_api.AffectedSourceFiles(build_file_filter):
     return []
@@ -98,7 +98,7 @@
 
   def file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=['.*[.]cc$', '.*[.]h$', tool])
+        x, files_to_check=['.*[.]cc$', '.*[.]h$', tool])
 
   if not input_api.AffectedSourceFiles(file_filter):
     return []
@@ -134,7 +134,7 @@
   ]
 
   def file_filter(x):
-    return input_api.FilterSourceFile(x, white_list=[r'.*\.h$', r'.*\.cc$'])
+    return input_api.FilterSourceFile(x, files_to_check=[r'.*\.h$', r'.*\.cc$'])
 
   errors = []
   for f in input_api.AffectedSourceFiles(file_filter):
@@ -151,7 +151,8 @@
   tool = 'tools/check_include_violations'
 
   def file_filter(x):
-    return input_api.FilterSourceFile(x, white_list=['include/.*[.]h$', tool])
+    return input_api.FilterSourceFile(
+        x, files_to_check=['include/.*[.]h$', tool])
 
   if not input_api.AffectedSourceFiles(file_filter):
     return []
@@ -165,7 +166,7 @@
 
   def file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=['protos/perfetto/.*[.]proto$', '.*[.]h', tool])
+        x, files_to_check=['protos/perfetto/.*[.]proto$', '.*[.]h', tool])
 
   if not input_api.AffectedSourceFiles(file_filter):
     return []
@@ -182,7 +183,7 @@
 
   def build_file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=['protos/perfetto/.*[.]proto$', tool])
+        x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
 
   if not input_api.AffectedSourceFiles(build_file_filter):
     return []
@@ -195,17 +196,17 @@
   return []
 
 
-# Prevent removing or changing lines in event_whitelist.
-def CheckWhitelist(input_api, output_api):
+# Prevent removing or changing lines in event_list.
+def CheckProtoEventList(input_api, output_api):
   for f in input_api.AffectedFiles():
-    if f.LocalPath() != 'tools/ftrace_proto_gen/event_whitelist':
+    if f.LocalPath() != 'tools/ftrace_proto_gen/event_list':
       continue
     if any((not new_line.startswith('removed')) and new_line != old_line
            for old_line, new_line in itertools.izip(f.OldContents(),
                                                     f.NewContents())):
       return [
           output_api.PresubmitError(
-              'event_whitelist only has two supported changes: '
+              'event_list only has two supported changes: '
               'appending a new line, and replacing a line with removed.')
       ]
   return []
@@ -216,7 +217,7 @@
 
   def file_filter(x):
     return input_api.FilterSourceFile(
-        x, white_list=['protos/perfetto/.*[.]proto$', tool])
+        x, files_to_check=['protos/perfetto/.*[.]proto$', tool])
 
   if not input_api.AffectedSourceFiles(file_filter):
     return []
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4c2f19d..8a5ab7e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -10,6 +10,9 @@
       "options": [
           {
 	      "include-filter": "FrameTracerTest.*"
+	  },
+	  {
+	      "include-filter": "FrameTimelineTest.*"
 	  }
       ]
     }
diff --git a/bazel/proto_gen.bzl b/bazel/proto_gen.bzl
index 50f5747..eb9d7e8 100644
--- a/bazel/proto_gen.bzl
+++ b/bazel/proto_gen.bzl
@@ -124,3 +124,30 @@
     output_to_genfiles = True,
     implementation = _proto_gen_impl,
 )
+
+
+def _proto_descriptor_gen_impl(ctx):
+    descriptors = [
+        f
+        for dep in ctx.attr.deps
+        for f in dep[ProtoInfo].transitive_descriptor_sets.to_list()
+    ]
+    ctx.actions.run_shell(
+        inputs=descriptors,
+        outputs=ctx.outputs.outs,
+        command='cat %s > %s' % (
+            ' '.join([f.path for f in descriptors]), ctx.outputs.outs[0].path)
+    )
+
+
+proto_descriptor_gen = rule(
+    implementation=_proto_descriptor_gen_impl,
+    attrs = {
+        "deps": attr.label_list(
+            mandatory = True,
+            allow_empty = False,
+            providers = [ProtoInfo],
+        ),
+        "outs": attr.output_list(mandatory=True),
+    }
+)
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 01c432f..1767aa4 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
-load("@perfetto//bazel:proto_gen.bzl", "proto_gen")
+load("@perfetto//bazel:proto_gen.bzl", "proto_descriptor_gen", "proto_gen")
 
 # +----------------------------------------------------------------------------+
 # | Base C++ rules.                                                            |
@@ -113,7 +113,7 @@
         name = name,
         srcs = [":" + name + "_src"],
         hdrs = [":" + name + "_h"],
-        deps = [PERFETTO_CONFIG.root + ":libprotozero"],
+        deps = [PERFETTO_CONFIG.root + ":protozero"],
         **kwargs
     )
 
@@ -158,12 +158,11 @@
         deps = [
             # Generated .ipc.{cc,h} depend on this and protozero.
             PERFETTO_CONFIG.root + ":perfetto_ipc",
-            PERFETTO_CONFIG.root + ":libprotozero",
+            PERFETTO_CONFIG.root + ":protozero",
         ] + _cc_deps,
         **kwargs
     )
 
-
 # Generates .gen.{cc,h} from .proto(s).
 def perfetto_cc_protocpp_library(name, deps, **kwargs):
     if _rule_override(
@@ -209,11 +208,42 @@
         srcs = [":" + name + "_gen"],
         textual_hdrs = [":" + name + "_gen_h"],
         deps = [
-            PERFETTO_CONFIG.root + ":libprotozero"
+            PERFETTO_CONFIG.root + ":protozero",
         ] + _cc_deps,
         **kwargs
     )
 
+def perfetto_proto_descriptor(name, deps, outs, **kwargs):
+    proto_descriptor_gen(
+        name = name,
+        deps = deps,
+        outs = outs,
+    )
+
+# Generator .descriptor.h from protos
+def perfetto_cc_proto_descriptor(name, deps, outs, **kwargs):
+    cmd = [
+        "$(location gen_cc_proto_descriptor_py)",
+        "--cpp_out=$@",
+        "--gen_dir=$(GENDIR)",
+        "$<"
+    ]
+    native.genrule(
+        name = name + "_gen",
+        cmd = " ".join(cmd),
+        exec_tools = [
+            ":gen_cc_proto_descriptor_py",
+        ],
+        srcs = deps,
+        outs = outs,
+    )
+
+    perfetto_cc_library(
+        name = name,
+        hdrs = [":" + name + "_gen"],
+        **kwargs
+    )
+
 # +----------------------------------------------------------------------------+
 # | Misc utility functions                                                     |
 # +----------------------------------------------------------------------------+
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index aef8ba2..2d71aa8 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -34,6 +34,12 @@
         # "perfetto_build_flags.h" file that can be included via:
         # #include "perfetto_build_flags.h".
         build_config = ["//:build_config_hdr"],
+
+        # Target exposing the PERFETTO_VERSION_STRING() and
+        # PERFETTO_VERSION_SCM_REVISION() macros. This is overridden in google
+        # internal builds.
+        version_header = ["//:cc_perfetto_version_header"],
+
         zlib = ["@perfetto_dep_zlib//:zlib"],
         jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
         linenoise = ["@perfetto_dep_linenoise//:linenoise"],
@@ -43,6 +49,7 @@
         protoc_lib = ["@com_google_protobuf//:protoc_lib"],
         protobuf_lite = ["@com_google_protobuf//:protobuf_lite"],
         protobuf_full = ["@com_google_protobuf//:protobuf"],
+        protobuf_descriptor_proto = ["@com_google_protobuf//:descriptor_proto"]
     ),
 
     # This struct allows embedders to customize the cc_opts for Perfetto
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index c3961bc..0888b62 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -1,4 +1,8 @@
 android-core/
+android-libbase/
+android-libprocinfo/
+android-logging/
+android-unwinding/
 android_sdk/
 aosp-*/
 benchmark/
@@ -11,6 +15,7 @@
 googletest/
 jsoncpp/
 libbacktrace/
+libbase/
 libcxx/
 libcxxabi/
 libfuzzer/
@@ -27,4 +32,5 @@
 sqlite_src/
 test_data/
 typefaces/
+win/
 zlib/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index e75f35a..c3ad7a8 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -37,16 +37,21 @@
   visibility = _buildtools_visibility
   if (is_clang) {
     cflags = [
-      "-Wno-unknown-warning-option",
-      "-Wno-global-constructors",
       "-Wno-covered-switch-default",
-      "-Wno-used-but-marked-unused",
+      "-Wno-deprecated-copy-dtor",
+      "-Wno-global-constructors",
       "-Wno-inconsistent-missing-override",
+      "-Wno-language-extension-token",
+      "-Wno-suggest-destructor-override",
+      "-Wno-suggest-override",
+      "-Wno-undef",
+      "-Wno-unknown-warning-option",
       "-Wno-unused-member-function",
-      "-Wno-zero-as-null-pointer-constant",
+      "-Wno-used-but-marked-unused",
       "-Wno-weak-vtables",
+      "-Wno-zero-as-null-pointer-constant",
     ]
-  } else {
+  } else if (!is_win) {
     cflags = [
       "-Wno-unknown-warning-option",
       "-Wno-deprecated-copy",
@@ -60,18 +65,18 @@
     # Using -isystem instead of include_dirs (-I), so we don't need to suppress
     # warnings coming from libunwindstack headers. Doing so would mask warnings
     # in our own code.
-    "-isystem",
-    rebase_path("android-core/libunwindstack/include", root_build_dir),
-    "-isystem",
-    rebase_path("android-core/libprocinfo/include", root_build_dir),
-    "-isystem",
-    rebase_path("android-core/base/include", root_build_dir),
-    "-isystem",
+    perfetto_isystem_cflag,
+    rebase_path("android-unwinding/libunwindstack/include", root_build_dir),
+    perfetto_isystem_cflag,
+    rebase_path("android-libprocinfo/include", root_build_dir),
+    perfetto_isystem_cflag,
+    rebase_path("android-libbase/include", root_build_dir),
+    perfetto_isystem_cflag,
     rebase_path("android-core/demangle/include", root_build_dir),
   ]
   if (is_android) {
     cflags += [
-      "-isystem",
+      perfetto_isystem_cflag,
       rebase_path("bionic/libc/include", root_build_dir),
     ]
   }
@@ -128,15 +133,31 @@
   configs = [ "//gn:protobuf_gen_config" ]
 
   defines = [ "HAVE_PTHREAD=1" ]
+  cflags = []
   if (is_clang) {
-    cflags = [
+    # We do NOT build libprotobuf with -Wextra or -Weverything. But still it
+    # hits some warnings that we need to suppress.
+    cflags += [
       "-Wno-unknown-warning-option",
       "-Wno-enum-compare-switch",
       "-Wno-user-defined-warnings",
       "-Wno-tautological-constant-compare",
+      "-Wno-inconsistent-missing-override",
     ]
-  } else {  # implies gcc
-    cflags = [ "-Wno-return-type" ]
+  } else if (!is_win) {  # implies gcc
+    cflags += [ "-Wno-return-type" ]
+  }
+  if (is_win) {
+    cflags += [ "/W0" ]
+  }
+}
+
+# Configuration propagated to targets depending on protobuf_full.
+config("protobuf_full_public_config") {
+  visibility = _buildtools_visibility
+  cflags = []
+  if (is_clang) {
+    cflags += [ "-Wno-switch-enum" ]
   }
 }
 
@@ -218,6 +239,10 @@
     "protobuf/src/google/protobuf/wire_format_lite.h",
   ]
   configs -= [ "//gn/standalone:extra_warnings" ]
+  if (is_win) {
+    # Protobuf has its own #define WIN32_LEAN_AND_MEAN.
+    configs -= [ "//gn/standalone:win32_lean_and_mean" ]
+  }
   configs += [ ":protobuf_config" ]
   public_configs = [ "//gn:protobuf_gen_config" ]
   deps = [ "//gn:default_deps" ]
@@ -346,8 +371,15 @@
     "protobuf/src/google/protobuf/wrappers.pb.h",
   ]
   configs -= [ "//gn/standalone:extra_warnings" ]
+  if (is_win) {
+    # Protobuf has its own #define WIN32_LEAN_AND_MEAN.
+    configs -= [ "//gn/standalone:win32_lean_and_mean" ]
+  }
   configs += [ ":protobuf_config" ]
-  public_configs = [ "//gn:protobuf_gen_config" ]
+  public_configs = [
+    "//gn:protobuf_gen_config",
+    ":protobuf_full_public_config",
+  ]
 }
 
 source_set("protoc_lib") {
@@ -525,8 +557,15 @@
     "protobuf/src/google/protobuf/compiler/zip_writer.h",
   ]
   configs -= [ "//gn/standalone:extra_warnings" ]
+  if (is_win) {
+    # Protobuf does has its own #define WIN32_LEAN_AND_MEAN.
+    configs -= [ "//gn/standalone:win32_lean_and_mean" ]
+  }
   configs += [ ":protobuf_config" ]
-  public_configs = [ "//gn:protobuf_gen_config" ]
+  public_configs = [
+    "//gn:protobuf_gen_config",
+    ":protobuf_full_public_config",
+  ]
 }
 
 if (current_toolchain == host_toolchain) {
@@ -538,6 +577,10 @@
     ]
     sources = [ "protobuf/src/google/protobuf/compiler/main.cc" ]
     configs -= [ "//gn/standalone:extra_warnings" ]
+    if (is_win) {
+      # Protobuf does has its own #define WIN32_LEAN_AND_MEAN.
+      configs -= [ "//gn/standalone:win32_lean_and_mean" ]
+    }
   }
 }  # host_toolchain
 
@@ -550,14 +593,17 @@
       "_LIBCXXABI_NO_EXCEPTIONS",
       "_LIBCPP_OVERRIDABLE_FUNC_VIS=__attribute__((__visibility__(\"default\")))",
     ]
-    cflags = [
-      "-fPIC",
-      "-fstrict-aliasing",
-    ]
+    cflags = [ "-fstrict-aliasing" ]
   }
 
   source_set("libunwind") {
     visibility = _buildtools_visibility
+    cflags = [
+      # libunwind expects to be compiled with unwind tables so it can
+      # unwind its own frames.
+      "-funwind-tables",
+      "-fstrict-aliasing",
+    ]
     sources = [
       "libunwind/src/Unwind-EHABI.cpp",
       "libunwind/src/Unwind-sjlj.c",
@@ -787,7 +833,7 @@
   include_dirs = [ "sqlite" ]
   cflags = [
     "-DSQLITE_THREADSAFE=0",
-    "-DQLITE_DEFAULT_MEMSTATUS=0",
+    "-DSQLITE_DEFAULT_MEMSTATUS=0",
     "-DSQLITE_LIKE_DOESNT_MATCH_BLOBS",
     "-DSQLITE_OMIT_DEPRECATED",
     "-DSQLITE_OMIT_SHARED_CACHE",
@@ -882,49 +928,59 @@
 source_set("zlib") {
   visibility = _buildtools_visibility
   sources = [
-    "zlib/src/adler32.c",
-    "zlib/src/compress.c",
-    "zlib/src/crc32.c",
-    "zlib/src/deflate.c",
-    "zlib/src/gzclose.c",
-    "zlib/src/gzlib.c",
-    "zlib/src/gzread.c",
-    "zlib/src/gzwrite.c",
-    "zlib/src/infback.c",
-    "zlib/src/inffast.c",
-    "zlib/src/inflate.c",
-    "zlib/src/inftrees.c",
-    "zlib/src/trees.c",
-    "zlib/src/uncompr.c",
-    "zlib/src/zutil.c",
+    "zlib/adler32.c",
+    "zlib/compress.c",
+    "zlib/cpu_features.c",
+    "zlib/crc32.c",
+    "zlib/deflate.c",
+    "zlib/gzclose.c",
+    "zlib/gzlib.c",
+    "zlib/gzread.c",
+    "zlib/gzwrite.c",
+    "zlib/infback.c",
+    "zlib/inffast.c",
+    "zlib/inflate.c",
+    "zlib/inftrees.c",
+    "zlib/trees.c",
+    "zlib/uncompr.c",
+    "zlib/zutil.c",
   ]
   configs -= [ "//gn/standalone:extra_warnings" ]
   cflags = []
   public_configs = [ ":zlib_config" ]
   deps = [ "//gn:default_deps" ]
+
+  # TODO(primiano): look into ADLER32_SIMD_SSSE3 and other SIMD optimizations
+  # (from chromium's third_party/zlib/BUILD.gn).
+  if (is_win) {
+    defines = [ "X86_WINDOWS" ]
+  }
 }
 
 config("zlib_config") {
   visibility = _buildtools_visibility
-  defines = [ "HAVE_HIDDEN" ]
+  if (!is_win) {
+    defines = [ "HAVE_HIDDEN" ]
+  }
   cflags = [
     # Using -isystem instead of include_dirs (-I), so we don't need to suppress
     # warnings coming from third-party headers. Doing so would mask warnings in
     # our own code.
-    "-isystem",
-    rebase_path("zlib/src", root_build_dir),
+    perfetto_isystem_cflag,
+    rebase_path("zlib", root_build_dir),
   ]
 }
 
 source_set("libunwindstack") {
   visibility = _buildtools_visibility
   include_dirs = [
-    "android-core/libunwindstack/include",
-    "android-core/libunwindstack",
-    "android-core/base/include",
-    "android-core/liblog/include",
-    "android-core/libprocinfo/include",
+    "android-unwinding/libunwindstack/include",
+    "android-unwinding/libunwindstack",
+    "android-libbase/include",
+    "android-logging/liblog/include",
+    "android-libprocinfo/include",
     "android-core/include",
+    "android-core/libcutils/include",
     "lzma/C",
   ]
   deps = [
@@ -932,44 +988,44 @@
     "//gn:default_deps",
   ]
   sources = [
-    "android-core/base/file.cpp",
-    "android-core/base/liblog_symbols.cpp",
-    "android-core/base/logging.cpp",
-    "android-core/base/stringprintf.cpp",
-    "android-core/base/strings.cpp",
-    "android-core/base/threads.cpp",
-    "android-core/liblog/fake_log_device.cpp",
-    "android-core/liblog/logger_write.cpp",
-    "android-core/libunwindstack/ArmExidx.cpp",
-    "android-core/libunwindstack/DwarfCfa.cpp",
-    "android-core/libunwindstack/DwarfEhFrameWithHdr.cpp",
-    "android-core/libunwindstack/DwarfMemory.cpp",
-    "android-core/libunwindstack/DwarfOp.cpp",
-    "android-core/libunwindstack/DwarfSection.cpp",
-    "android-core/libunwindstack/Elf.cpp",
-    "android-core/libunwindstack/ElfInterface.cpp",
-    "android-core/libunwindstack/ElfInterfaceArm.cpp",
-    "android-core/libunwindstack/Global.cpp",
-    "android-core/libunwindstack/JitDebug.cpp",
-    "android-core/libunwindstack/LocalUnwinder.cpp",
-    "android-core/libunwindstack/Log.cpp",
-    "android-core/libunwindstack/MapInfo.cpp",
-    "android-core/libunwindstack/Maps.cpp",
-    "android-core/libunwindstack/Memory.cpp",
-    "android-core/libunwindstack/Regs.cpp",
-    "android-core/libunwindstack/RegsArm.cpp",
-    "android-core/libunwindstack/RegsArm64.cpp",
-    "android-core/libunwindstack/RegsMips.cpp",
-    "android-core/libunwindstack/RegsMips64.cpp",
-    "android-core/libunwindstack/RegsX86.cpp",
-    "android-core/libunwindstack/RegsX86_64.cpp",
-    "android-core/libunwindstack/Symbols.cpp",
-    "android-core/libunwindstack/Unwinder.cpp",
+    "android-libbase/file.cpp",
+    "android-libbase/liblog_symbols.cpp",
+    "android-libbase/logging.cpp",
+    "android-libbase/stringprintf.cpp",
+    "android-libbase/strings.cpp",
+    "android-libbase/threads.cpp",
+    "android-logging/liblog/logger_write.cpp",
+    "android-logging/liblog/properties.cpp",
+    "android-unwinding/libunwindstack/ArmExidx.cpp",
+    "android-unwinding/libunwindstack/DwarfCfa.cpp",
+    "android-unwinding/libunwindstack/DwarfEhFrameWithHdr.cpp",
+    "android-unwinding/libunwindstack/DwarfMemory.cpp",
+    "android-unwinding/libunwindstack/DwarfOp.cpp",
+    "android-unwinding/libunwindstack/DwarfSection.cpp",
+    "android-unwinding/libunwindstack/Elf.cpp",
+    "android-unwinding/libunwindstack/ElfInterface.cpp",
+    "android-unwinding/libunwindstack/ElfInterfaceArm.cpp",
+    "android-unwinding/libunwindstack/Global.cpp",
+    "android-unwinding/libunwindstack/JitDebug.cpp",
+    "android-unwinding/libunwindstack/LocalUnwinder.cpp",
+    "android-unwinding/libunwindstack/Log.cpp",
+    "android-unwinding/libunwindstack/MapInfo.cpp",
+    "android-unwinding/libunwindstack/Maps.cpp",
+    "android-unwinding/libunwindstack/Memory.cpp",
+    "android-unwinding/libunwindstack/Regs.cpp",
+    "android-unwinding/libunwindstack/RegsArm.cpp",
+    "android-unwinding/libunwindstack/RegsArm64.cpp",
+    "android-unwinding/libunwindstack/RegsMips.cpp",
+    "android-unwinding/libunwindstack/RegsMips64.cpp",
+    "android-unwinding/libunwindstack/RegsX86.cpp",
+    "android-unwinding/libunwindstack/RegsX86_64.cpp",
+    "android-unwinding/libunwindstack/Symbols.cpp",
+    "android-unwinding/libunwindstack/Unwinder.cpp",
   ]
   if (current_cpu == "x86") {
-    sources += [ "android-core/libunwindstack/AsmGetRegsX86.S" ]
+    sources += [ "android-unwinding/libunwindstack/AsmGetRegsX86.S" ]
   } else if (current_cpu == "x64") {
-    sources += [ "android-core/libunwindstack/AsmGetRegsX86_64.S" ]
+    sources += [ "android-unwinding/libunwindstack/AsmGetRegsX86_64.S" ]
   }
   configs -= [
     "//gn/standalone:extra_warnings",
@@ -984,7 +1040,7 @@
 config("bionic_kernel_uapi_headers") {
   visibility = _buildtools_visibility
   cflags = [
-    "-isystem",
+    perfetto_isystem_cflag,
     rebase_path("bionic/libc/kernel", root_build_dir),
   ]
 }
@@ -997,7 +1053,7 @@
     # Using -isystem instead of include_dirs (-I), so we don't need to suppress
     # warnings coming from third-party headers. Doing so would mask warnings in
     # our own code.
-    "-isystem",
+    perfetto_isystem_cflag,
     rebase_path("jsoncpp/include", root_build_dir),
   ]
 }
@@ -1020,7 +1076,7 @@
     # Using -isystem instead of include_dirs (-I), so we don't need to suppress
     # warnings coming from third-party headers. Doing so would mask warnings in
     # our own code.
-    "-isystem",
+    perfetto_isystem_cflag,
     rebase_path("linenoise", root_build_dir),
   ]
 }
diff --git a/debian/changelog b/debian/changelog
index 6e2b4fc..40a8df5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+perfetto (9.0-1) unstable; urgency=medium
+
+  * Run traced under a dedicated system user account and set socket
+    permissions accordingly so that any user can write trace events, but
+    only the users in the "traced-consumer" group can read trace data.
+  * Update to debhelper 10.
+  * Bump version to match Perfetto release.
+
+ -- Sami Kyostila <skyostil@google.com>  Mon, 9 Nov 2020 15:24:00 +0000
+
 perfetto (0.1-1) unstable; urgency=medium
 
   * Initial release
diff --git a/debian/compat b/debian/compat
index ec63514..f599e28 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-9
+10
diff --git a/debian/control b/debian/control
index b16354f..be52af4 100644
--- a/debian/control
+++ b/debian/control
@@ -1,16 +1,16 @@
 Source: perfetto
-Section: unknown
+Section: kernel
 Priority: optional
 Maintainer: Sami Kyostila <skyostil@google.com>
-Build-Depends: debhelper (>= 9)
+Build-Depends: debhelper (>= 10)
 Standards-Version: 3.9.8
-Homepage: https://android.googlesource.com/platform/external/perfetto/
+Homepage: https://perfetto.dev
 Vcs-Git: https://android.googlesource.com/platform/external/perfetto/
 Vcs-Browser: https://android.googlesource.com/platform/external/perfetto/
 
 Package: perfetto
 Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}
-Description: Performance instrumentation and logging for POSIX platforms
+Description: Performance instrumentation and logging framework
  Perfetto is a performance instrumentation and logging framework for POSIX
  systems.
diff --git a/debian/postinst b/debian/postinst
new file mode 100755
index 0000000..c32e828
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+adduser --quiet --system --no-create-home --group traced
+addgroup --quiet --system traced-consumer
+usermod -a -G traced-consumer traced
diff --git a/debian/postrm b/debian/postrm
new file mode 100755
index 0000000..20c9767
--- /dev/null
+++ b/debian/postrm
@@ -0,0 +1,3 @@
+#!/bin/sh
+set -e
+rm -f /tmp/perfetto-consumer /tmp/perfetto-producer
diff --git a/debian/traced-probes.service b/debian/traced-probes.service
index 3209123..81a23f7 100644
--- a/debian/traced-probes.service
+++ b/debian/traced-probes.service
@@ -1,5 +1,5 @@
 [Unit]
-Description=Perfetto probes daemon
+Description=Perfetto data sources for system tracing (ftrace and /proc pollers)
 
 [Service]
 ExecStart=/usr/sbin/traced_probes
diff --git a/debian/traced.service b/debian/traced.service
index b082e9c..6eb7d8a 100644
--- a/debian/traced.service
+++ b/debian/traced.service
@@ -1,10 +1,11 @@
 [Unit]
-Description=Perfetto trace daemon
+Description=Perfetto tracing service daemon
 
 [Service]
-ExecStart=/usr/sbin/traced
-User=nobody
-Group=nogroup
+ExecStart=/usr/sbin/traced \
+    --set-socket-permissions traced:0666:traced-consumer:0660
+User=traced
+Group=traced
 PrivateTmp=no
 PrivateDevices=yes
 PrivateNetwork=yes
diff --git a/docs/analysis/metrics.md b/docs/analysis/metrics.md
index 910d2e6..32d52e9 100644
--- a/docs/analysis/metrics.md
+++ b/docs/analysis/metrics.md
@@ -88,7 +88,7 @@
 
 ## Walkthrough: prototyping a metric
 
-TIP: To see how to add to add a new metric to trace processor, see the checklist
+TIP: To see how to add a new metric to trace processor, see the checklist
 [here](/docs/contributing/common-tasks.md#new-metric)
 
 This walkthrough will outline how to prototype a metric locally without needing
@@ -177,11 +177,11 @@
 sum of the CPU time they ran for and the number of threads which were associated
 with the process.
 
-The following SQL should added to a file called `top_five_processes.sql` in the
-workspace:
+The following SQL should be added to a file called `top_five_processes.sql` in
+the workspace:
 
 ```sql
-CREATE VIEW top_five_processes_by_cpu
+CREATE VIEW top_five_processes_by_cpu AS
 SELECT
   process.name as process_name,
   CAST(SUM(sched.dur) / 1e6 as INT64) as cpu_time_ms,
diff --git a/docs/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index 1214d59..49e2f5e 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -291,7 +291,7 @@
 
 WARNING: An important restriction on span joined tables is that spans from
 the same table in the same partition *cannot* overlap. For performance
-reasons, span join does attempt to dectect and error out in this situation;
+reasons, span join does not attempt to detect and error out in this situation;
 instead, incorrect rows will silently be produced.
 
 ### Ancestor slice
@@ -347,6 +347,31 @@
 FROM interesting_slices
 ```
 
+### Following/Preceding/Connected flows
+following_flow, preceding_flow, connected_flow are custom operator tables that
+take a [slice table's id column](/docs/analysis/sql-tables.autogen#slice) and
+collect all entries of [flow table](/docs/analysis/sql-tables.autogen#flow),
+that are directly or indirectly connected to the given starting slice.
+
+`FOLLOWING_FLOW(start_slice_id)` - contains all entries of
+[flow table](/docs/analysis/sql-tables.autogen#flow)
+that are present in any chain of kind: `flow[0] -> flow[1] -> ... -> flow[n]`,
+where `flow[i].slice_out = flow[i+1].slice_in` and
+`flow[0].slice_out = start_slice_id`.
+
+`PRECEDING_FLOW(start_slice_id)` - contains all entries of
+[flow table](/docs/analysis/sql-tables.autogen#flow)
+that are present in any chain of kind: `flow[n] -> flow[n-1] -> ... -> flow[0]`,
+where `flow[i].slice_in = flow[i+1].slice_out` and
+`flow[0].slice_in = start_slice_id`.
+
+`CONNECTED_FLOW(start_slice_id)` - contains a union of both
+`FOLLOWING_FLOW(start_slice_id)` and `PRECEDING_FLOW(start_slice_id)` tables.
+
+```sql
+--number of following flows for each slice
+SELECT (SELECT COUNT(*) FROM FOLLOWING_FLOW(slice_id)) as following FROM slice;
+```
 
 ## Metrics
 
@@ -421,6 +446,188 @@
       and alerts added on these instead; this is because the trace processor
       storage is monotonic-append-only.
 
+## Python API
+
+The trace processor Python API is built on the existing HTTP interface of `trace processor`
+and is available as part of the standalone build. The API allows you to load in traces and
+query tables and run metrics without requiring the `trace_processor` binary to be
+downloaded or installed.
+
+### Setup
+```
+pip install perfetto
+```
+NOTE: The API is only compatible with Python3.
+
+```python
+from perfetto.trace_processor import TraceProcessor
+# Initialise TraceProcessor with a trace file
+tp = TraceProcessor(file_path='trace.pftrace')
+```
+
+NOTE: The TraceProcessor can be initialized in a combination of ways including:
+      <br> - An address at which there exists a running instance of `trace_processor` with a
+      loaded trace (e.g. `TraceProcessor(addr='localhost:9001')`)
+      <br> - An address at which there exists a running instance of `trace_processor` and
+      needs a trace to be loaded in
+      (e.g. `TraceProcessor(addr='localhost:9001', file_path='trace.pftrace')`)
+      <br> - A path to a `trace_processor` binary and the trace to be loaded in
+      (e.g. `TraceProcessor(bin_path='./trace_processor', file_path='trace.pftrace')`)
+
+
+### API
+
+The `trace_processor.api` module contains the `TraceProcessor` class which provides various
+functions that can be called on the loaded trace. For more information on how to use
+these functions, see this [`example`](/src/trace_processor/python/example.py).
+
+#### Query
+The query() function takes an SQL query as input and returns an iterator through the rows
+of the result.
+
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+qr_it = tp.query('SELECT ts, dur, name FROM slice')
+for row in qr_it:
+  print(row.ts, row.dur, row.name)
+```
+**Output**
+```
+261187017446933 358594 eglSwapBuffersWithDamageKHR
+261187017518340 357 onMessageReceived
+261187020825163 9948 queueBuffer
+261187021345235 642 bufferLoad
+261187121345235 153 query
+...
+```
+The QueryResultIterator can also be converted to a Pandas DataFrame, although this
+requires you to have both the `NumPy` and `Pandas` modules installed.
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+qr_it = tp.query('SELECT ts, dur, name FROM slice')
+qr_df = qr_it.as_pandas_dataframe()
+print(qr_df.to_string())
+```
+**Output**
+```
+ts                   dur                  name
+-------------------- -------------------- ---------------------------
+     261187017446933               358594 eglSwapBuffersWithDamageKHR
+     261187017518340                  357 onMessageReceived
+     261187020825163                 9948 queueBuffer
+     261187021345235                  642 bufferLoad
+     261187121345235                  153 query
+     ...
+```
+Furthermore, you can use the query result in a Pandas DataFrame format to easily
+make visualisations from the trace data.
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+qr_it = tp.query('SELECT ts, value FROM counter WHERE track_id=50')
+qr_df = qr_it.as_pandas_dataframe()
+qr_df = qr_df.replace(np.nan,0)
+qr_df = qr_df.set_index('ts')['value'].plot()
+```
+**Output**
+
+![Graph made frpm the query results](/docs/images/example_pd_graph.png)
+
+
+#### Metric
+The metric() function takes in a list of trace metrics and returns the results as a Protobuf.
+
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+ad_cpu_metrics = tp.metric(['android_cpu'])
+print(ad_cpu_metrics)
+```
+**Output**
+```
+metrics {
+  android_cpu {
+    process_info {
+      name: "/system/bin/init"
+      threads {
+        name: "init"
+        core {
+          id: 1
+          metrics {
+            mcycles: 1
+            runtime_ns: 570365
+            min_freq_khz: 1900800
+            max_freq_khz: 1900800
+            avg_freq_khz: 1902017
+          }
+        }
+        core {
+          id: 3
+          metrics {
+            mcycles: 0
+            runtime_ns: 366406
+            min_freq_khz: 1900800
+            max_freq_khz: 1900800
+            avg_freq_khz: 1902908
+          }
+        }
+        ...
+      }
+      ...
+    }
+    process_info {
+      name: "/system/bin/logd"
+      threads {
+        name: "logd.writer"
+        core {
+          id: 0
+          metrics {
+            mcycles: 8
+            runtime_ns: 33842357
+            min_freq_khz: 595200
+            max_freq_khz: 1900800
+            avg_freq_khz: 1891825
+          }
+        }
+        core {
+          id: 1
+          metrics {
+            mcycles: 9
+            runtime_ns: 36019300
+            min_freq_khz: 1171200
+            max_freq_khz: 1900800
+            avg_freq_khz: 1887969
+          }
+        }
+        ...
+      }
+      ...
+    }
+    ...
+  }
+}
+```
+
+### HTTP
+The `trace_processor.http` module contains the `TraceProcessorHttp` class which
+provides methods to make HTTP requests to an address at which there already
+exists a running instance of `trace_processor` with a trace loaded in. All
+results are returned in Protobuf format
+(see [`trace_processor_proto`](/protos/perfetto/trace_processor/trace_processor.proto)).
+Some functions include:
+* `execute_query()` - Takes in an SQL query and returns a `QueryResult` Protobuf
+  message
+* `compute_metric()` - Takes in a list of trace metrics and returns a
+  `ComputeMetricResult` Protobuf message
+* `status()` - Returns a `StatusResult` Protobuf message
+
+
 ## Testing
 
 Trace processor is mainly tested in two ways:
diff --git a/docs/case-studies/memory.md b/docs/case-studies/memory.md
index c2e8391..bb6744d 100644
--- a/docs/case-studies/memory.md
+++ b/docs/case-studies/memory.md
@@ -3,6 +3,8 @@
 ## Prerequisites
 
 * A host running macOS or Linux.
+* [ADB](https://developer.android.com/studio/command-line/adb) installed and
+  in PATH.
 * A device running Android 11+.
 
 If you are profiling your own app and are not running a userdebug build of
diff --git a/docs/concepts/config.md b/docs/concepts/config.md
index cb15859..9711281 100644
--- a/docs/concepts/config.md
+++ b/docs/concepts/config.md
@@ -415,16 +415,18 @@
 Start triggers allow activating a tracing session only after some significant
 event has happened. Passing a trace config that has `START_TRACING` trigger
 causes the tracing session to stay idle (i.e. not recording any data) until either
-the trigger is hit or the `duration_ms` timeout is hit.
+the trigger is hit or the `trigger_timeout_ms` timeout is hit.
+
+`trace_duration_ms` and triggered traces can not be used at the same time.
 
 Example config:
 ```protobuf
-// If no trigger is hit, the trace will end without having recorded any data
-// after 30s.
-duration_ms: 30000
+# If no trigger is hit, the trace will end without having recorded any data
+# after 30s.
+trigger_timeout_ms: 30000
 
-// If the "myapp_is_slow" is hit, the trace starts recording data and will be
-// stopped after 5s.
+# If the "myapp_is_slow" is hit, the trace starts recording data and will be
+# stopped after 5s.
 trigger_config {
   trigger_mode: START_TRACING
   triggers {
@@ -433,7 +435,7 @@
   }
 }
 
-// The rest of the config is as usual.
+# The rest of the config is as usual.
 buffers { ... }
 data_sources { ... }
 ```
@@ -453,10 +455,10 @@
 
 Example config:
 ```protobuf
-// If no trigger is hit, the trace will end after 30s.
-duration_ms: 30000
+# If no trigger is hit, the trace will end after 30s.
+trigger_timeout_ms: 30000
 
-// If the "missed_frame" is hit, the trace is stopped after 1s.
+# If the "missed_frame" is hit, the trace is stopped after 1s.
 trigger_config {
   trigger_mode: STOP_TRACING
   triggers {
@@ -465,7 +467,7 @@
   }
 }
 
-// The rest of the config is as usual.
+# The rest of the config is as usual.
 buffers { ... }
 data_sources { ... }
 ```
diff --git a/docs/contributing/build-instructions.md b/docs/contributing/build-instructions.md
index 109cff3..76d53e3 100644
--- a/docs/contributing/build-instructions.md
+++ b/docs/contributing/build-instructions.md
@@ -3,6 +3,8 @@
 The source of truth for the Perfetto codebase lives in AOSP:
 https://android.googlesource.com/platform/external/perfetto/
 
+A read-only mirror is also available at https://github.com/google/perfetto .
+
 Perfetto can be built both from the Android tree (AOSP) and standalone.
 Standalone builds are meant only for local testing and are not shipped.
 Due to the reduced dependencies they are faster to iterate on and the
@@ -10,51 +12,57 @@
 
 ## Get the code
 
-**Standalone checkout**:  
-```
-$ git clone https://android.googlesource.com/platform/external/perfetto/
+**Standalone checkout**:
+
+```bash
+git clone https://android.googlesource.com/platform/external/perfetto/
 ```
 
-**Android tree**:  
-Perfetto lives in `external/perfetto` in the AOSP tree.
+**Android tree**:
+
+Perfetto lives in [`external/perfetto` in the AOSP tree](https://cs.android.com/android/platform/superproject/+/master:external/perfetto/).
 
 ## Prerequisites
 
-**Standalone checkout**:  
+**Standalone checkout**:
+
 All dependent libraries are self-hosted and pulled through:
-```
-$ tools/install-build-deps [--android] [--ui]
+
+```bash
+tools/install-build-deps [--android] [--ui]
 ```
 
-**Android tree**:  
+**Android tree**:
+
 See https://source.android.com/setup
 
-
 ## Building
 
-**Standalone checkout**:  
+**Standalone checkout**:
+
 If you are a chromium developer and have depot_tools installed you can avoid
 the `tools/` prefix below and just use gn/ninja from depot_tools.
 
 `$ tools/gn args out/android` to generate build files and enter in the editor:
 
-```
+```python
 target_os = "android"                 # Only when building for Android
-target_cpu = "arm" / "arm64" / "x64"  # Only when building for Android
+target_cpu = "arm" / "arm64" / "x64"
 is_debug = true / false
 cc_wrapper = "ccache"                 # Optionally speed repeated builds with ccache
 ```
 
 (See the [Build Configurations](#build-configurations) section below for more)
 
-```
-$ tools/ninja -C out/android
+```bash
+tools/ninja -C out/android
 ```
 
-**Android tree**:  
-`$ mmma external/perfetto`
+**Android tree**
+
+`mmma external/perfetto`
 or
-`$ m perfetto traced traced_probes`
+`m perfetto traced traced_probes`
 
 This will generate artifacts `out/target/product/XXX/system/`.
 Executables and shared libraries are stripped by default by the Android build
@@ -62,26 +70,37 @@
 
 ## UI development
 
-To build the UI (remember to run `tools/install-build-deps --ui` first):
+This command pulls the UI-related dependencies (notably, the NodeJS binary)
+and installs the `node_modules` in `ui/node_modules`:
 
+```bash
+tools/install-build-deps --ui
 ```
-$ tools/ninja -C out/android ui
 
+Build the UI:
+
+```bash
+gn args out/default  # The only relevant arg is is_debug=true|false
+
+# This will generate the static content for serving the UI in out/default/ui/.
+tools/ninja -C out/default ui
 ```
+
 Test your changes on a local server using:
 
+```bash
+ui/run-dev-server out/default
 ```
-$ ui/run-dev-server out/android
-```
-Navigate to `localhost:10000` to see the changes.
+
+Navigate to http://localhost:10000/ to see the changes.
 
 ## IDE setup
 
 Use a following command in the checkout directory in order to generate the
 compilation database file:
 
-```
-$ tools/ninja -C out/android -t compdb cc cxx > compile_commands.json
+```bash
+tools/ninja -C out/default -t compdb cc cxx > compile_commands.json
 ```
 
 After generating, it can be used in CLion (File -> Open -> Open As Project),
@@ -90,90 +109,192 @@
 
 ## Build files
 
-The source of truth of our build file is in the BUILD.gn files, which are based on [GN][gn-quickstart].
-The Android build file ([Android.bp](/Android.bp)) is autogenerated from the GN files
-through `tools/gen_android_bp`, which needs to be invoked whenever a change
-touches GN files or introduces new ones.  
+The source of truth of our build file is in the BUILD.gn files, which are based
+on [GN][gn-quickstart].
+The Android build file ([Android.bp](/Android.bp)) is autogenerated from the GN
+files through `tools/gen_android_bp`, which needs to be invoked whenever a
+change touches GN files or introduces new ones.
+
 A presubmit check checks that the Android.bp is consistent with GN files when
-submitting a CL through `git cl upload`.  
-The generator has a whitelist of root targets that will be translated into the
+submitting a CL through `git cl upload`.
+
+The generator has a list of root targets that will be translated into the
 Android.bp file. If you are adding a new target, add a new entry to the
-`default_targets` variable inside [tools/gen\_android\_bp](/tools/gen_android_bp).
+`default_targets` variable in [`tools/gen_android_bp`](/tools/gen_android_bp).
 
 ## Supported platforms
 
-**Linux desktop** (Debian Rodete):
-  - Hermetic clang + libcxx toolchain (both following chromium's revisions)
-  - GCC-7 and libstdc++ 6
+**Linux desktop** (Debian Rodete)
 
-**Android**:
-  - Android's NDK r15c (using NDK's libcxx)
-  - AOSP's in-tree clang (using in-tree libcxx)
+- Hermetic clang + libcxx toolchain (both following chromium's revisions)
+- GCC-7 and libstdc++ 6
 
-**Mac**:
- - XCode 9 / clang (currently maintained best-effort).
+**Android**
+
+- Android's NDK r15c (using NDK's libcxx)
+- AOSP's in-tree clang (using in-tree libcxx)
+
+**Mac**
+
+- XCode 9 / clang (currently maintained best-effort).
+
+**Windows**
+
+Windows builds are not currently supported when using the standalone checkout
+and GN. Windows is supported only for a subset of the targets (mainly
+`trace_processor` and the in-process version of the
+[Tracing SDK](/docs/instrumentation/tracing-sdk.md)) in two ways:
+(1) when building through Bazel; (2) when building as part of Chromium.
 
 ## Build configurations
 
-TIP: `tools/build_all_configs.py` can be used to generate out/XXX folders for most of
-the supported configurations.
+TIP: `tools/build_all_configs.py` can be used to generate out/XXX folders for
+most of the supported configurations.
 
 The following [GN args][gn-quickstart] are supported:
 
-`target_os = "android" | "linux" | "mac"`:  
+`target_os = "android" | "linux" | "mac"`:
+
 Defaults to the current host, set "android" to build for Android.
 
-`target_cpu = "arm" | "arm64" | "x86" | "x64"`:  
+`target_cpu = "arm" | "arm64" | "x64"`
+
 Defaults to `"arm"` when `target_os` == `"android"`, `"x64"` when targeting the
 host. 32-bit host builds are not supported.
+Note: x64 here really means x86_64. This is to keep it consistent with
+Chromium's choice, which in turn follows Windows naming convention.
 
-`is_debug = true | false`:  
-Toggles Debug (default) / Release mode.
+`is_debug = true | false`
 
-`is_clang = true | false`:  
+Toggles Debug (default) / Release mode. This affects, among other things:
+(i) the `-g` compiler flag; (ii) setting/unsetting `-DNDEBUG`; (iii) turning
+on/off `DCHECK` and `DLOG`.
+Note that debug builds of Perfetto are sensibly slower than release versions. We
+strongly encourage using debug builds only for local development.
+
+`is_clang = true | false`
+
 Use Clang (default: true) or GCC (false).
 On Linux, by default it uses the self-hosted clang (see `is_hermetic_clang`).
 On Android, by default it uses clang from the NDK (in `buildtools/ndk`).
 On Mac, by default it uses the system version of clang (requires Xcode).
+See also the [custom toolchain](#custom-toolchain) section below.
 
-`is_hermetic_clang = true | false`:  
+`is_hermetic_clang = true | false`
+
 Use bundled toolchain from `buildtools/` rather than system-wide one.
 
-`cc = "gcc" / cxx = "g++"`:  
-Uses a different compiler binary (default: autodetected depending on is_clang).
+`cc = "gcc" / cxx = "g++"`
 
-`cc_wrapper = "tool"`:  
+Uses a different compiler binary (default: autodetected depending on is_clang).
+See also the [custom toolchain](#custom-toolchain) section below.
+
+`cc_wrapper = "tool_name"`
+
 Prepends all build commands with a wrapper command. Using `"ccache"` here
 enables the [ccache](https://github.com/ccache/ccache) caching compiler,
 which can considerably speed up repeat builds.
 
-`is_asan = true`:  
+`is_asan = true`
+
 Enables [Address Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer)
 
-`is_lsan = true`:  
+`is_lsan = true`
+
 Enables [Leak Sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
 (Linux/Mac only)
 
-`is_msan = true`:  
+`is_msan = true`
+
 Enables [Memory Sanitizer](https://github.com/google/sanitizers/wiki/MemorySanitizer)
 (Linux only)
 
-`is_tsan = true`:  
+`is_tsan = true`
+
 Enables [Thread Sanitizer](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual)
 (Linux/Mac only)
 
-`is_ubsan = true`:  
+`is_ubsan = true`
+
 Enables [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html)
 
-`extra_{cflags,cxxflags, ldflags} = "-fXXX"`:  
-This is the moral equivalent of the common Makefile practice:
-`CFLAGS=...` `CXXFLAGS=...` `LDFLAGS=...`.
-If you want to propagate these env variables into GN, set:
-`extra_cxxflags=\"$CXXFLAGS\"`.
-See the [build_fuzzers](/infra/oss-fuzz/build_fuzzers) script for an example.
-Note that these flags will be appended both to target and host toolchains.
-If you want to append flags only to one toolchain use, respectively,
-`extra_target_{cflags,cxxflags,ldflags}` and
-`extra_host_{cflags,cxxflags,ldflags}`.
+### {#custom-toolchain} Using custom toolchains and CC / CXX / CFLAGS env vars
+
+When building Perfetto as part of some other build environment it might be
+necessary to switch off all the built-in toolchain-related path-guessing scripts
+and manually specify the path of the toolchains.
+
+```python
+# Disable the scripts that guess the path of the toolchain.
+is_system_compiler = true
+
+ar = "/path/to/ar"
+cc = "/path/to/gcc-like-compiler"
+cxx = "/path/to/g++-like-compiler"
+linker = ""  # This is passed to -fuse-ld=...
+```
+
+If you are using a build system that keeps the toolchain settings in
+environment variables, you can set:
+
+```python
+is_system_compiler = true
+ar="${AR}"
+cc="${CC}"
+cxx="${CXX}"
+```
+
+`is_system_compiler = true` can be used also for cross-compilation.
+In case of cross-compilation, the GN variables have the following semantic:
+`ar`, `cc`, `cxx`, `linker` refer to the _host_ toolchain (sometimes also called
+_build_ toolchain). This toolchain is used to build: (i) auxiliary tools
+(e.g. the `traceconv` conversion util) and (ii) executable artifacts that are
+used during the rest of the build process for the target (e.g., the `protoc`
+compiler or the `protozero_plugin` protoc compiler plugin).
+
+The cross-toolchain used to build the artifacts that run on the device is
+prefixed by `target_`: `target_ar`, `target_cc`, `target_cxx`, `target_linker`.
+
+```python
+# Cross compilation kicks in when at least one of these three variables is set
+# to a value != than the host defaults.
+
+target_cpu = "x86" | "x64" | "arm" | "arm64"
+target_os = "linux" | "android"
+target_triplet =  "arm-linux-gnueabi" | "x86_64-linux-gnu" | ...
+```
+
+When integrating with GNU Makefile cross-toolchains build environments, a
+typical mapping of the corresponding environment variables is:
+
+```python
+ar="${BUILD_AR}"
+cc="${BUILD_CC}"
+cxx="${BUILD_CXX}"
+target_ar="${AR}"
+target_cc="${CC}"
+target_cxx="${CXX}"
+```
+
+It is possible to extend the set of `CFLAGS` and `CXXFLAGS` through the
+`extra_xxxflags` GN variables as follows. The extra flags are always appended
+(hence, take precedence) to the set of flags that the GN build files generate.
+
+```python
+# These apply both to host and target toolchain.
+extra_cflags="${CFLAGS}"
+extra_cxxflags="${CXXFLAGS}"
+extra_ldflags="${LDFLAGS}"
+
+# These apply only to the host toolchain.
+extra_host_cflags="${BUILD_CFLAGS}"
+extra_host_cxxflags="${BUILD_CXXFLAGS}"
+extra_host_ldflags="${BUILD_LDFLAGS}"
+
+# These apply only to the target toolchain.
+extra_target_cflags="${CFLAGS}"
+extra_target_cxxflags="${CXXFLAGS} ${debug_flags}"
+extra_target_ldflags="${LDFLAGS}"
+```
 
 [gn-quickstart]: https://gn.googlesource.com/gn/+/master/docs/quick_start.md
diff --git a/docs/contributing/chrome-branches.md b/docs/contributing/chrome-branches.md
new file mode 100644
index 0000000..3530f5f
--- /dev/null
+++ b/docs/contributing/chrome-branches.md
@@ -0,0 +1,71 @@
+# Branching Perfetto for Chrome milestones
+
+Merging a (set of) Perfetto change(s) to a Chrome milestone release requires
+creation of a branch in the perfetto repo, cherry-picking of the change(s) to
+the branch, and updating the `DEPS` file in Chrome's milestone branch to point
+to the new perfetto branch's head.
+
+## Creating the perfetto branch {#branch}
+
+1.  Determine the branch name: **`chromium/XXXX`**, where `XXXX` is the branch
+    number of the milestone (see
+    [Chromium Dash](https://chromiumdash.appspot.com/branches)). Example for
+    M87: `chromium/4280`.
+
+1.  Check if the branch already exists: if yes, skip to
+    [cherry-picking](#all-tables). To check, you can search for it in
+    [Gerrit's branch page](https://android-review.googlesource.com/admin/repos/platform/external/perfetto,branches).
+
+1.  Look up the appropriate base revision for the branch. You should use the
+    revision that Chromium's `DEPS` of the milestone branch points to (search
+    for `perfetto` in the file). The `DEPS` file for branch XXXX is at:
+
+    `https://chromium.googlesource.com/chromium/src.git/+/refs/branch-heads/XXXX/DEPS`
+
+    Example for M87:
+    [`DEPS`](https://chromium.googlesource.com/chromium/src.git/+/refs/branch-heads/4280/DEPS)
+    (at time of writing) points to `f4cf78e052c9427d8b6c49faf39ddf2a2e236069`.
+
+1.  Create the branch - the easiest way to do this is via
+    [Gerrit's branch page](https://android-review.googlesource.com/admin/repos/platform/external/perfetto,branches).
+    The `NEW BRANCH` button on the top right opens a wizard - fill in the branch
+    name and base revision determined above. If this fails with a permission
+    issue, contact the [Discord chat](https://discord.gg/35ShE3A) or
+    [perfetto-dev](https://groups.google.com/forum/#!forum/perfetto-dev) mailing
+    list.
+
+## Cherry-picking the change(s) {#cherry-pick}
+
+1.  If there are no merge conflicts, cherry-picking via Gerrit will be easiest.
+    To attempt this, open your change in Gerrit and use the `Cherry pick` entry
+    in the overflow menu on the top right, providing the `chromium/XXXX` branch
+    name (see [above](#branch)).
+
+1.  Otherwise, merge the patch locally into a branch tracking
+    `origin/chromium/XXXX` and upload a Gerrit change as usual:
+
+    ```
+    $ git fetch origin
+    $ git checkout -tb cpick origin/chromium/XXXX
+    $ git cherry-pick -x <commit hash>    # Resolve conflicts manually.
+    $ tools/gen_all out/xxx               # If necessary.
+    $ git cl upload    # Remove "Change-Id:" lines from commit message.
+    ```
+
+1.  Send the patch for review and land it. Note the commit's revision hash.
+
+## Updating the DEPS file in Chromium
+
+1.  Create, send for review, and land a Chromium patch that edits the top-level
+    `DEPS` file on the Chromium's milestone branch. You can also combine this
+    step with cherry-picks of any chromium changes. For details, see
+    [Chromium's docs](https://www.chromium.org/developers/how-tos/drover). It
+    amounts to:
+
+    ```
+    $ gclient sync --with_branch_heads
+    $ git fetch
+    $ git checkout -tb perfetto_uprev refs/remotes/branch-heads/XXXX
+    $ ...    # Edit DEPS.
+    $ git cl upload
+    ```
diff --git a/docs/contributing/common-tasks.md b/docs/contributing/common-tasks.md
index 31987c8..f0e99ef 100644
--- a/docs/contributing/common-tasks.md
+++ b/docs/contributing/common-tasks.md
@@ -6,7 +6,7 @@
 
 1. Find the `format` file for your event. The location of the file depends where `tracefs` is mounted but can often be found at `/sys/kernel/debug/tracing/events/EVENT_GROUP/EVENT_NAME/format`.
 2. Copy the format file into the codebase at `src/traced/probes/ftrace/test/data/synthetic/events/EVENT_GROUP/EVENT_NAME/format`.
-3. Add the event to [tools/ftrace_proto_gen/event_whitelist](/tools/ftrace_proto_gen/event_whitelist).
+3. Add the event to [tools/ftrace_proto_gen/event_list](/tools/ftrace_proto_gen/event_list).
 4. Run `tools/run_ftrace_proto_gen`. This will update `protos/perfetto/trace/ftrace/ftrace_event.proto` and `protos/perfetto/trace/ftrace/GROUP_NAME.proto`.
 5. Run `tools/gen_all out/YOUR_BUILD_DIRECTORY`. This will update `src/traced/probes/ftrace/event_info.cc` and `protos/perfetto/trace/perfetto_trace.proto`.
 6. If special handling in `trace_processor` is desired update [src/trace_processor/importers/ftrace/ftrace_parser.cc](/src/trace_processor/importers/ftrace/ftrace_parser.cc) to parse the event.
diff --git a/docs/data-sources/atrace.md b/docs/data-sources/atrace.md
index 0df13c7..51e50f1 100644
--- a/docs/data-sources/atrace.md
+++ b/docs/data-sources/atrace.md
@@ -98,19 +98,25 @@
 ## TraceConfig
 
 ```protobuf
+buffers {
+  size_kb: 102400
+  fill_policy: RING_BUFFER
+}
+
 data_sources {
   config {
     name: "linux.ftrace"
     ftrace_config {
-      // Enables specific system events tags.
+      # Enables specific system events tags.
       atrace_categories: "am"
       atrace_categories: "pm"
 
-      // Enables events for a specific app.
+      # Enables events for a specific app.
       atrace_apps: "com.google.android.apps.docs"
 
-      // Enables all events for all apps.
+      # Enables all events for all apps.
       atrace_apps: "*"
     }
   }
+}
 ```
diff --git a/docs/data-sources/java-heap-profiler.md b/docs/data-sources/java-heap-profiler.md
index 5168d7d..f03977e 100644
--- a/docs/data-sources/java-heap-profiler.md
+++ b/docs/data-sources/java-heap-profiler.md
@@ -53,6 +53,18 @@
 Note that this is **experimental** and the **API is subject to change**.
 From this we can see how much memory is being held by each type of object
 
+For that, we need to find the timestamp and upid of the graph.
+
+```sql
+select distinct graph_sample_ts, upid from heap_graph_object
+```
+
+graph_sample_ts     |        upid        |
+--------------------|--------------------|
+     56785646801    |         1          |
+
+We can then use them to get the flamegraph data.
+
 ```sql
 select name, cumulative_size
        from experimental_flamegraph(56785646801, 1, 'graph')
diff --git a/docs/data-sources/native-heap-profiler.md b/docs/data-sources/native-heap-profiler.md
index f582dc3..1146a90 100644
--- a/docs/data-sources/native-heap-profiler.md
+++ b/docs/data-sources/native-heap-profiler.md
@@ -56,7 +56,7 @@
 
 #### Using the tools/heap_profile script (recommended)
 
-On Linux / MacOS, use the `tools/heap_profile` script. If you are having trouble
+You can use the `tools/heap_profile` script. If you are having trouble
 make sure you are using the
 [latest version](
 https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile).
@@ -142,9 +142,13 @@
 ## Runtime profiling
 
 When a profiling session is started, all matching processes (by name or PID)
-are enumerated and profiling is enabled. The resulting profile will contain
-all allocations done between the beginning and the end of the profiling
-session.
+are enumerated and are signalled to request profiling. Profiling isn't actually
+enabled until a few hundred milliseconds after the next allocation that is
+done by the application. If the application is idle when profiling is
+requested, and then does a burst of allocations, these may be missed.
+
+The resulting profile will contain all allocations done between when profiling
+is enabled, and the end of the profiling session.
 
 The resulting [ProfilePacket] will have `from_startup` set to false in the
 corresponding `ProcessHeapSamples` message. This does not get surfaced in the
@@ -179,8 +183,8 @@
 Profiling requests for non-profileable/debuggable processes will result in an
 empty profile.
 
-On userdebug builds, all processes except for a small blacklist of critical
-services can be profiled (to find the blacklist, look for
+On userdebug builds, all processes except for a small set of critical
+services can be profiled (to find the set of disallowed targets, look for
 `never_profile_heap` in [heapprofd.te](
 https://cs.android.com/android/platform/superproject/+/master:system/sepolicy/private/heapprofd.te?q=never_profile_heap).
 This restriction can be lifted by disabling SELinux by running
@@ -295,6 +299,26 @@
 an ELF file with the given build id. This way, you will not have to worry
 about correct filenames.
 
+## Deobfuscation
+
+If your profile contains obfuscated Java methods (like `fsd.a`), you can
+provide a deobfuscation map to turn them back into human readable.
+To do so, use the `PERFETTO_PROGUARD_MAP` environment variable, using the
+format `packagename=filename[:packagename=filename...]`, e.g.
+`PERFETTO_PROGUARD_MAP=com.example.pkg1=foo.txt:com.example.pkg2=bar.txt`.
+All tools
+(traceconv, trace_processor_shell, the heap_profile script) support specifying
+the `PERFETTO_PROGUARD_MAP` as an environment variable.
+
+You can get a deobfuscation map for your trace using
+`tools/traceconv deobfuscate`. Then concatenate the resulting file to your
+trace to get a deobfuscated version of it.
+
+```
+PERFETTO_PROGUARD_MAP=com.example.pkg tools/traceconv deobfuscate ${TRACE} > deobfuscation_map
+cat ${TRACE} deobfuscation_map > deobfuscated_trace
+```
+
 ## Troubleshooting
 
 ### Buffer overrun
@@ -309,7 +333,7 @@
 ### Profile is empty
 
 Check whether your target process is eligible to be profiled by consulting
-[Target processes](#target-processes) above.
+[Target processes](#heapprofd-targets) above.
 
 Also check the [Known Issues](#known-issues).
 
@@ -363,14 +387,86 @@
 If this does not show one or more of the sections, change your build system
 to not strip them.
 
+## (non-Android) Linux support
+
+NOTE: This is experimental and only for ad-hoc investigations.
+
+You can use a standalone library to profile memory allocations on Linux.
+First [build Perfetto](/docs/contributing/build-instructions.md)
+
+```
+tools/build_all_configs.py
+ninja -C out/linux_clang_release
+```
+
+Then, run traced
+
+```
+out/linux_clang_release/traced
+```
+
+Start the profile (e.g. targeting trace_processor_shell)
+
+```
+out/linux_clang_release/perfetto \
+  -c - --txt \
+  -o ~/heapprofd-trace \
+<<EOF
+
+buffers {
+  size_kb: 32768
+}
+
+data_sources {
+  config {
+    name: "android.heapprofd"
+    heapprofd_config {
+      shmem_size_bytes: 8388608
+      sampling_interval_bytes: 4096
+      block_client: true
+      process_cmdline: "trace_processor_shell"
+      dump_at_max: true
+    }
+  }
+}
+
+duration_ms: 604800000
+write_into_file: true
+flush_timeout_ms: 30000
+flush_period_ms: 604800000
+
+EOF
+```
+
+Finally, run your target (e.g. trace_processor_shell) with LD_PRELOAD
+
+```
+LD_PRELOAD=out/linux_clang_release/libheapprofd_preload.so out/linux_clang_release/trace_processor_shell <trace>
+```
+
+Then, Ctrl-C the Perfetto invocation and upload ~/heapprofd-trace to the
+[Perfetto UI](https://ui.perfetto.dev).
+
 ## Known Issues
 
-### Android 10
+### {#known-issues-android11} Android 11
 
-* On ARM32, the bottom-most frame is always `ERROR 2`. This is harmless and
-  the callstacks are still complete.
+* 32-bit programs cannot be targeted on 64-bit devices.
+* Setting `sampling_interval_bytes` to 0 crashes the target process.
+  This is an invalid config that should be rejected instead.
+* For startup profiles, some frame names might be missing. This will be
+  resolved in Android 12.
+
+### {#known-issues-android10} Android 10
+* Function names in libraries with load bias might be incorrect. Use
+  [offline symbolization](#symbolization) to resolve this issue.
+* For startup profiles, some frame names might be missing. This will be
+  resolved in Android 12.
+* 32-bit programs cannot be targeted on 64-bit devices.
 * x86 platforms are not supported. This includes the Android _Cuttlefish_
   emulator.
+* On ARM32, the bottom-most frame is always `ERROR 2`. This is harmless and
+  the callstacks are still complete.
 * If heapprofd is run standalone (by running `heapprofd` in a root shell, rather
   than through init), `/dev/socket/heapprofd` get assigned an incorrect SELinux
   domain. You will not be able to profile any processes unless you disable
@@ -380,6 +476,8 @@
   memory in the child process will prematurely end the profile.
   `java.lang.Runtime.exec` does this, calling it will prematurely end
   the profile. Note that this is in violation of the POSIX standard.
+* Setting `sampling_interval_bytes` to 0 crashes the target process.
+  This is an invalid config that should be rejected instead.
 
 ## Heapprofd vs malloc_info() vs RSS
 
diff --git a/docs/data-sources/system-log.md b/docs/data-sources/system-log.md
index 73d30c9..9c713d9 100644
--- a/docs/data-sources/system-log.md
+++ b/docs/data-sources/system-log.md
@@ -35,20 +35,6 @@
 }
 ```
 
-A wildcard can be used to collect all events in a category:
-
-```protobuf
-data_sources {
-  config {
-    name: "linux.ftrace"
-    ftrace_config {
-      ftrace_events: "ftrace/print"
-      ftrace_events: "sched/*"
-    }
-  }
-}
-```
-
 The full configuration options for ftrace can be seen in [ftrace_config.proto](/protos/perfetto/config/ftrace/ftrace_config.proto).
 
 ## Android system logs
diff --git a/docs/design-docs/heapprofd-design.md b/docs/design-docs/heapprofd-design.md
index 1f11bf4..c4bbd29 100644
--- a/docs/design-docs/heapprofd-design.md
+++ b/docs/design-docs/heapprofd-design.md
@@ -109,7 +109,7 @@
 
 Remote unwinding also enables us to use _global caching_ (`Elf::SetCachingEnabled(true)`) in libunwindstack. This prevents debug information being used by different processes to be loaded and decompressed multiple times.
 
-We add an `FDMaps` objects to parse maps from `/proc/self/maps` sent by the target process. We keep `FDMaps` object cached per process that is being profiled. This both saves the overhead of text-parsing `/proc/[pid]/maps` as well as keeps various objects needed for unwinding (e.g. decompressed minidebuginfo). In case an unwind fails with `ERROR_INVALID_MAP` we reparse the maps object. We will  do changes to libunwindstack to create a more general version of [`LocalUpdatableMaps`](https://cs.android.com/android/platform/superproject/+/master:system/core/libunwindstack/Maps.cpp?q=symbol:LocalUpdatableMaps) that is also applicable for remote processes.
+We add an `FDMaps` objects to parse maps from `/proc/self/maps` sent by the target process. We keep `FDMaps` object cached per process that is being profiled. This both saves the overhead of text-parsing `/proc/[pid]/maps` as well as keeps various objects needed for unwinding (e.g. decompressed minidebuginfo). In case an unwind fails with `ERROR_INVALID_MAP` we reparse the maps object. We will  do changes to libunwindstack to create a more general version of [`LocalUpdatableMaps`](https://cs.android.com/android/platform/superproject/+/master:system/unwinding/libunwindstack/Maps.cpp?q=symbol:LocalUpdatableMaps) that is also applicable for remote processes.
 
 
 #### Advantages of remote unwinding
@@ -218,7 +218,7 @@
 
 The most efficient way of stack unwinding is using frame pointers. This is unreliable on Android as we do not control build parameters for vendor libraries or OEM builds and due to issues on ARM32. Thus, our stack unwinding relies on libunwindstack which uses DWARF information from the library ELF files to determine return addresses. This can significantly slower, with unwinding of a stack taking between 100μs and ~100 ms ([data from simpleperf](https://gist.github.com/segfaulthunter/a3a5a352196f9037f34241f8fb09004d)).
 
-[libunwindstack](https://cs.android.com/android/platform/superproject/+/master:system/core/libunwindstack/) is Android's replacement for [libunwind](https://www.nongnu.org/libunwind/). It has a modern C++ object-oriented API surface and support for Android specific features allowing it to unwind mixed native and Java applications using information emitted by ART depending on execution mode. It also supports symbolization for native code and all three execution modes or ART.
+[libunwindstack](https://cs.android.com/android/platform/superproject/+/master:system/unwinding/libunwindstack/) is Android's replacement for [libunwind](https://www.nongnu.org/libunwind/). It has a modern C++ object-oriented API surface and support for Android specific features allowing it to unwind mixed native and Java applications using information emitted by ART depending on execution mode. It also supports symbolization for native code and all three execution modes or ART.
 
 ### Symbolization
 Symbolization is the process of determining function name and line number from a code address. For builds by Google, we can get symbolized binaries (i.e. binaries with an ELF section that can be used for symbolization) from go/ab or https://ci.android.com (e.g. https://ci.android.com/builds/submitted/6410994/aosp_cf_x86_phone-userdebug/latest/aosp_cf_x86_phone-symbols-6410994.zip).
diff --git a/docs/design-docs/life-of-a-tracing-session.md b/docs/design-docs/life-of-a-tracing-session.md
index 66690ec..79bdeb6 100644
--- a/docs/design-docs/life-of-a-tracing-session.md
+++ b/docs/design-docs/life-of-a-tracing-session.md
@@ -51,7 +51,7 @@
     buffer.
 18. The service will check if the given chunk, identified by the tuple
     `{ProducerID (unspoofable), WriterID, ChunkID}` is still present in the
-    trace buffer and if so will proceed to patch it (% sanity checks).
+    trace buffer and if so will proceed to patch it (% checks).
 19. The consumer sends a [`FlushRequest`](/protos/perfetto/ipc/consumer_port.proto#52)
     to the service, asking it commit all data on flight in the trace buffers.
 20. The service, in turn, issues a
diff --git a/docs/design-docs/protozero.md b/docs/design-docs/protozero.md
index d77da48..fa87fe2 100644
--- a/docs/design-docs/protozero.md
+++ b/docs/design-docs/protozero.md
@@ -397,7 +397,7 @@
   void Append(T x) {
     // The memcpy will be elided by the compiler, which will emit just a
     // 64-bit aligned mov instruction.
-    memcpy(reinterpret_cast<T*>(ptr_), &x, sizeof(x));
+    memcpy(reinterpret_cast<void*>(ptr_), &x, sizeof(x));
     ptr_ += sizeof(x);
   }
 
@@ -407,7 +407,7 @@
   void set_field_uint64(uint64_t x) { Append(x); }
   void set_field_string(const char* str) { ptr_ = strcpy(ptr_, str); }
 
-  char storage_[sizeof(g_fake_input_simple)];
+  alignas(uint64_t) char storage_[sizeof(g_fake_input_simple) + 8];
   char* ptr_ = &storage_[0];
 };
 ```
diff --git a/docs/images/debug-slices-random.png b/docs/images/debug-slices-random.png
new file mode 100644
index 0000000..88c0eca
--- /dev/null
+++ b/docs/images/debug-slices-random.png
Binary files differ
diff --git a/docs/images/example_pd_graph.png b/docs/images/example_pd_graph.png
new file mode 100644
index 0000000..e8d55c2
--- /dev/null
+++ b/docs/images/example_pd_graph.png
Binary files differ
diff --git a/docs/images/rail-mode-debug-slices.png b/docs/images/rail-mode-debug-slices.png
new file mode 100644
index 0000000..608d9ac
--- /dev/null
+++ b/docs/images/rail-mode-debug-slices.png
Binary files differ
diff --git a/docs/instrumentation/heapprofd-api.md b/docs/instrumentation/heapprofd-api.md
new file mode 100644
index 0000000..ffcd77f
--- /dev/null
+++ b/docs/instrumentation/heapprofd-api.md
@@ -0,0 +1,144 @@
+# heapprofd Custom Allocator API - Early Access
+
+WARNING: The heapprofd Custom Allocator API is currently in **pre-alpha**
+         stage. The API is subject to change, and the implementation might
+         still be unstable. Please file
+         [bugs](https://github.com/google/perfetto/issues/new) for any
+         issues you encounter.
+
+NOTE: The heapprofd Custom Allocator API requires a device running Android
+      10 or newer.
+
+## Give us a head's up
+
+Thanks for trying out the Custom Allocator API! As this is pre-alpha, we
+would ask you to [file a tracking GitHub issue](
+https://github.com/google/perfetto/issues/new) so we can communicate with you
+in case we have updated information. You do not need to wait for any reply on
+the bug before proceeding.
+
+## Get SDK
+
+Before instrumenting your app, you need to get the heapprofd library and
+header.
+
+### Option 1: Prebuilts
+
+You can download the library as a binary from [Google Drive](
+https://drive.google.com/drive/folders/15RPlGgAHWRSk7KquBqlQ7fsCaXnNaa6r
+).
+Join our [Google Group](https://groups.google.com/forum/#!forum/perfetto-dev)
+to get access.
+
+### Option 2: Build yourself (on Linux)
+
+Alternatively, you can build the binaries yourself from AOSP.
+
+First, [check out AOSP](https://source.android.com/setup/build/downloading):
+
+```
+$ mkdir aosp && cd aosp
+$ repo init -u  https://android.googlesource.com/platform/manifest -b master --partial-clone
+$ repo sync -c -j8
+```
+
+Then, build `heapprofd_standalone_client` for arm64.
+
+```
+$ source build/envsetup.sh
+$ lunch aosp_arm64-eng
+$ make heapprofd_standalone_client
+```
+
+You will find the built library in
+`out/target/product/generic_arm64/system/lib64/heapprofd_standalone_client.so`.
+The header for the API can be found in
+`external/perfetto/include/perfetto/profiling/memory/heap_profile.h`.
+
+WARNING: Only use the header from the checkout you used to build the library,
+         as the API is not stable yet.
+
+To make debugging in the future easier, make note of the state of your
+checkout at the time you built.
+
+```
+repo info > repo-info.txt
+```
+Please attach this file to any bugs you file.
+
+## Instrument App
+
+Let's assume your application has a very simple custom allocator that looks
+like this:
+
+```
+void* my_malloc(size_t size) {
+  void* ptr = [code to somehow allocate get size bytes];
+  return ptr;
+}
+
+void my_free(void* ptr) {
+  [code to somehow free ptr]
+}
+```
+
+To find out where in a program these two functions get called, we instrument
+the allocator using this API:
+
+```
+#include "path/to/heap_profile.h"
+
+static uint32_t g_heap_id = AHeapProfile_registerHeap(
+  AHeapInfo_create("invalid.example"));
+void* my_malloc(size_t size) {
+  void* ptr = [code to somehow allocate get size bytes];
+  AHeapProfile_reportAllocation(g_heap_id, static_cast<uintptr_t>(ptr), size);
+  return ptr;
+}
+
+void my_free(void* ptr) {
+  AHeapProfile_reportFree(g_heap_id, static_cast<uintptr_t>(ptr));
+  [code to somehow free ptr]
+}
+```
+
+Don't forget to link `heapprofd_standalone_client.so` and including it in
+your app.
+
+## Profile your App
+
+Then, use the [heap_profile](
+https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+script to get a profile to generate textpb of the config.
+To convert to a binary proto, you additionally need to download
+[`perfetto_trace.proto`](
+https://raw.githubusercontent.com/google/perfetto/master/protos/perfetto/trace/perfetto_trace.proto)
+and have recent version of the protoc compiler installed.
+[Learn how to install protoc](https://grpc.io/docs/protoc-installation).
+
+On Linux, you can start a profile using the following pipeline (substitue
+`$APP_NAME` for the name of your app and `$HEAP` for the name of the heap
+you registered using `AHeapProfile_registerHeap`):
+
+```
+heap_profile -n $APP_NAME --heaps $HEAP --print-config | \
+ path/to/protoc --encode=perfetto.protos.TraceConfig perfetto_trace.proto | \
+ adb shell perfetto -c - -o /data/misc/perfetto-traces/profile
+```
+
+On Windows, you will need [python 3.6](https://www.python.org/downloads/) or
+later. You can start a profile using the following pipeline from a command
+prompt (substitue`%APP_NAME%` for the name of your app and `%HEAP%` for
+the name of the heap you registered using `AHeapProfile_registerHeap`):
+
+```
+python /path/to/heap_profile -n %APP_NAME% --heaps %HEAP% --print-config | ^
+ path/to/protoc --encode=perfetto.protos.TraceConfig perfetto_trace.proto | ^
+ adb shell perfetto -c - -o /data/misc/perfetto-traces/profile
+```
+
+Play around with the app to make it cause custom allocations, then stop the
+profile using `adb shell killall perfetto`. Once it is done, pull the profile
+from `/data/misc/perfetto-traces/profile` using `adb pull`.
+
+Upload the profile to the [Perfetto UI](https://ui.perfetto.dev).
diff --git a/docs/instrumentation/interceptors.md b/docs/instrumentation/interceptors.md
new file mode 100644
index 0000000..f61c469
--- /dev/null
+++ b/docs/instrumentation/interceptors.md
@@ -0,0 +1,143 @@
+# Trace packet interceptors (Tracing SDK)
+
+A trace packet interceptor is used to redirect trace packets written by a
+data source into a custom backend instead of the normal Perfetto tracing
+service. For example, the console interceptor prints all trace packets to the
+console as they are generated. Another potential use is exporting trace data
+to another tracing service such as Android ATrace or Windows ETW.
+
+An interceptor is defined by subclassing the `perfetto::Interceptor` template:
+
+```C++
+class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+ public:
+  ~MyInterceptor() override = default;
+
+  // This function is called for each intercepted trace packet. |context|
+  // contains information about the trace packet as well as other state
+  // tracked by the interceptor (e.g., see ThreadLocalState).
+  //
+  // Intercepted trace data is provided in the form of serialized protobuf
+  // bytes, accessed through the |context.packet_data| field.
+  //
+  // Warning: this function can be called on any thread at any time. See
+  // below for how to safely access shared interceptor data from here.
+  static void OnTracePacket(InterceptorContext context) {
+    perfetto::protos::pbzero::TracePacket::Decoder packet(
+        context.packet_data.data, context.packet_data.size);
+    // ... Write |packet| to the desired destination ...
+  }
+};
+```
+
+An interceptor should be registered before any tracing sessions are started.
+Note that the interceptor also needs to be activated through the trace config
+shown below.
+
+```C++
+perfetto::InterceptorDescriptor desc;
+desc.set_name("my_interceptor");
+MyInterceptor::Register(desc);
+```
+
+Finally, an interceptor is enabled through the trace config like this:
+
+```C++
+perfetto::TraceConfig cfg;
+auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ds_cfg->set_name("data_source_to_intercept");   // e.g. "track_event"
+ds_cfg->mutable_interceptor_config()->set_name("my_interceptor");
+```
+
+Once an interceptor is enabled, all data from the affected data sources is
+sent to the interceptor instead of the main tracing buffer.
+
+## Interceptor state
+
+Besides the serialized trace packet data, the `OnTracePacket` interceptor
+function can access three other types of state:
+
+1. **Global state:** this is no different from a normal static function, but
+   care must be taken because |OnTracePacket| can be called concurrently on
+   any thread at any time.
+
+2. **Per-data source instance state:** since the interceptor class is
+   automatically instantiated for each intercepted data source, its fields
+   can be used to store per-instance data such as the trace config. This data
+   can be maintained through the OnSetup/OnStart/OnStop callbacks:
+
+   ```C++
+   class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+    public:
+     void OnSetup(const SetupArgs& args) override {
+       enable_foo_ = args.config.interceptor_config().enable_foo();
+     }
+
+     bool enable_foo_{};
+   };
+   ```
+
+   In the interceptor function this data must be accessed through a scoped
+   lock for safety:
+
+   ```C++
+   class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+     ...
+     static void OnTracePacket(InterceptorContext context) {
+       auto my_interceptor = context.GetInterceptorLocked();
+       if (my_interceptor) {
+          // Access fields of MyInterceptor here.
+          if (my_interceptor->enable_foo_) { ... }
+       }
+       ...
+     }
+   };
+   ```
+
+   Since accessing this data involves holding a lock, it should be done
+   sparingly.
+
+3. **Per-thread/TraceWriter state:** many data sources use interning to avoid
+   repeating common data in the trace. Since the interning dictionaries are
+   typically kept individually for each TraceWriter sequence (i.e., per
+   thread), an interceptor can declare a data structure with lifetime
+   matching the TraceWriter:
+
+   ```C++
+   class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+    public:
+     struct ThreadLocalState
+         : public perfetto::InterceptorBase::ThreadLocalState {
+       ThreadLocalState(ThreadLocalStateArgs&) override = default;
+       ~ThreadLocalState() override = default;
+
+       std::map<size_t, std::string> event_names;
+     };
+   };
+   ```
+
+   This per-thread state can then be accessed and maintained in
+   `OnTracePacket` like this:
+
+   ```C++
+   class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+     ...
+     static void OnTracePacket(InterceptorContext context) {
+       // Updating interned data.
+       auto& tls = context.GetThreadLocalState();
+       if (parsed_packet.sequence_flags() & perfetto::protos::pbzero::
+               TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+         tls.event_names.clear();
+       }
+       for (const auto& entry : parsed_packet.interned_data().event_names())
+         tls.event_names[entry.iid()] = entry.name();
+
+       // Looking up interned data.
+       if (parsed_packet.has_track_event()) {
+         size_t name_iid = parsed_packet.track_event().name_iid();
+         const std::string& event_name = tls.event_names[name_iid];
+       }
+       ...
+     }
+   };
+   ```
\ No newline at end of file
diff --git a/docs/instrumentation/track-events.md b/docs/instrumentation/track-events.md
index 1e673a0..6cf61e7 100644
--- a/docs/instrumentation/track-events.md
+++ b/docs/instrumentation/track-events.md
@@ -15,8 +15,8 @@
 section of the Tracing SDK page for instructions on how to check out and
 build the SDK.
 
-TIP: The code from this example is also available as a
-     [GitHub repository](https://github.com/skyostil/perfetto-sdk-example).
+TIP: The code from these examples is also available [in the
+repository](/examples/sdk/README.md).
 
 There are a few main types of track events:
 
@@ -437,4 +437,62 @@
 Note that interned data is strongly typed, i.e., each class of interned data
 uses a separate namespace for identifiers.
 
-[RAII]: https://en.cppreference.com/w/cpp/language/raii
+### Tracing session observers
+
+The session observer interface allows applications to be notified when track
+event tracing starts and stops:
+
+```C++
+class Observer : public perfetto::TrackEventSessionObserver {
+  public:
+  ~Observer() override = default;
+
+  void OnSetup(const perfetto::DataSourceBase::SetupArgs&) override {
+    // Called when tracing session is configured. Note tracing isn't active yet,
+    // so track events emitted here won't be recorded.
+  }
+
+  void OnStart(const DataSourceBase::SetupArgs&) override {
+    // Called when a tracing session is started. It is possible to emit track
+    // events from this callback.
+  }
+
+  void OnStop(const DataSourceBase::StartArgs&) override {
+    // Called when a tracing session is stopped. It is still possible to emit
+    // track events from this callback.
+  }
+};
+```
+
+Note that all methods of the interface are called on an internal Perfetto
+thread.
+
+For example, here's how to wait for any tracing session to start:
+
+```C++
+class Observer : public perfetto::TrackEventSessionObserver {
+ public:
+  Observer() { perfetto::TrackEvent::AddSessionObserver(this); }
+  ~Observer() { perfetto::TrackEvent::RemoveSessionObserver(this); }
+
+  void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.notify_one();
+  }
+
+  void WaitForTracingStart() {
+    printf("Waiting for tracing to start...\n");
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [] { return perfetto::TrackEvent::IsEnabled(); });
+    printf("Tracing started\n");
+  }
+
+  std::mutex mutex;
+  std::condition_variable cv;
+};
+
+Observer observer;
+observer.WaitForTracingToStart();
+```
+
+[RAII]: https://en.cppreference.com/w/cpp/language/raii
\ No newline at end of file
diff --git a/docs/quickstart/android-tracing.md b/docs/quickstart/android-tracing.md
index ee91c9e..471f8cf 100644
--- a/docs/quickstart/android-tracing.md
+++ b/docs/quickstart/android-tracing.md
@@ -126,12 +126,12 @@
 In all other cases, first push the trace config file and then invoke perfetto:
 ```bash
 adb push config.txt /data/local/tmp/trace_config.txt
-adb shell 'perfetto --txt -c - -o /data/misc/perfetto-traces/trace < /data/local/tmp/trace_config.txt'
+adb shell 'cat /data/local/tmp/trace_config.txt | perfetto --txt -c - -o /data/misc/perfetto-traces/trace'
 ```
 
-NOTE: because of strict SELinux rules, on versions of older than Android 11
-(R) passing directly the file path as `-c /data/local/tmp/config` might fail,
-hence the `-c -` + stdin piping above.
+NOTE: because of strict SELinux rules, on non-rooted builds of Android, passing
+directly the file path as `-c /data/local/tmp/config` will fail, hence the
+`-c -` + stdin piping above.
 
 Pull the file using `adb pull /data/misc/perfetto-traces/trace ~/trace.pftrace`
 and upload to the [Perfetto UI](https://ui.perfetto.dev).
diff --git a/docs/quickstart/heap-profiling.md b/docs/quickstart/heap-profiling.md
index 6e9bb26..6f9692f 100644
--- a/docs/quickstart/heap-profiling.md
+++ b/docs/quickstart/heap-profiling.md
@@ -2,7 +2,7 @@
 
 ## Prerequisites
 
-* A host running macOS or Linux.
+* [ADB](https://developer.android.com/studio/command-line/adb) installed.
 * A device running Android 10+.
 * A _Profileable_ or _Debuggable_ app. If you are running on a _"user"_ build of
   Android (as opposed to _"userdebug"_ or _"eng"_), your app needs to be marked
@@ -13,20 +13,54 @@
 
 ## Capture a heap profile
 
-Download the `tools/heap_profile` (if you don't have a perfetto checkout) and
-run it as follows:
+### Linux / macOS
+Make sure adb is installed and in your PATH.
+
+```bash
+adb devices -l
+```
+
+If more than one device or emulator is reported you must select one upfront as follows:
+
+```bash
+export ANDROID_SERIAL=SER123456
+```
+
+Download the `tools/heap_profile` (if you don't have a perfetto checkout):
 
 ```bash
 curl -LO https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile
 chmod +x heap_profile
+```
 
+Then start the profile:
+
+```bash
 ./heap_profile -n system_server
+```
 
-Profiling active. Press Ctrl+C to terminate.
-You may disconnect your device.
+### Windows
 
-Wrote profiles to /tmp/profile-1283e247-2170-4f92-8181-683763e17445 (symlink /tmp/heap_profile-latest)
-These can be viewed using pprof. Googlers: head to pprof/ and upload them.
+Make sure that the downloaded adb.exe is in the PATH.
+
+```bash
+set PATH=%PATH%;%USERPROFILE%\Downloads\platform-tools
+
+adb devices -l
+```
+
+If more than one device or emulator is reported you must select one upfront as follows:
+
+```bash
+set ANDROID_SERIAL=SER123456
+```
+
+Download the
+[heap_profile](https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile)
+script. Then start the profile:
+
+```bash
+python /path/to/heap_profile -n system_server 
 ```
 
 ## View profile
diff --git a/docs/quickstart/trace-analysis.md b/docs/quickstart/trace-analysis.md
index 3f3a41f..a46a249 100644
--- a/docs/quickstart/trace-analysis.md
+++ b/docs/quickstart/trace-analysis.md
@@ -1,14 +1,16 @@
 # Quickstart: SQL-based analysis and trace-based metrics
 
-_This quickstart explains how to use `trace_processor` to programmatically query
-the trace contents through SQL and compute trace-based metrics._
+_This quickstart explains how to use `trace_processor` as well as its Python API to 
+programmatically query the trace contents through SQL and compute trace-based metrics._
 
-## Get Trace Processor
+## Trace Processor
 
 TraceProcessor is a multi-format trace importing and query engine based on
 SQLite. It comes both as a C++ library and as a standalone executable:
 `trace_processor_shell` (or just `trace_processor`).
 
+### Setup
+
 ```bash
 # Download prebuilts (Linux and Mac only)
 curl -LO https://get.perfetto.dev/trace_processor
@@ -30,12 +32,12 @@
 See [Trace Processor docs](/docs/analysis/trace-processor.md) for the full
 TraceProcessor guide.
 
-## Sample queries
+### Sample queries
 
 For more exhaustive examples see the _SQL_ section of the various _Data sources_
 docs.
 
-### Slices
+#### Slices
 
 Slices are stackable events which have name and span some duration of time.
 
@@ -53,7 +55,7 @@
      ...
 ```
 
-### Counters
+#### Counters
 
 Counters are events with a value which changes over time.
 
@@ -72,7 +74,7 @@
 ...
 ```
 
-### Scheduler slices
+#### Scheduler slices
 
 Scheduler slices indicate which thread was scheduled on which CPU at which time.
 
@@ -90,7 +92,7 @@
 ...
 ```
 
-## Trace-based metrics
+### Trace-based metrics
 
 Trace Processor offers also a higher-level query interface that allows to run
 pre-baked queries, herein called "metrics". Metrics are generally curated by
@@ -104,7 +106,7 @@
 The corresponding SQL queries live in
 [/src/trace_processor/metrics](/src/trace_processor/metrics/).
 
-### Run a single metric
+#### Run a single metric
 
 Let's run the [`android_cpu`](/protos/perfetto/metrics/android/cpu_metric.proto)
 metric. This metrics computes the total CPU time and the total cycles
@@ -175,7 +177,7 @@
 }
 ```
 
-### Running multiple metrics
+#### Running multiple metrics
 
 Multiple metrics can be flagged using comma separators to the `--run-metrics`
 flag. This will output a text proto with the combined result of running both
@@ -236,7 +238,7 @@
 }
 ```
 
-### JSON and binary output
+#### JSON and binary output
 
 The trace processor also supports binary protobuf and JSON as alternative output
 formats. This is useful when the intended reader is an offline tool.
@@ -313,6 +315,105 @@
 }
 ```
 
+## Python API
+
+The API can be run without requiring the `trace_processor` binary to be
+downloaded or installed.
+
+### Setup
+```
+$ pip install perfetto
+```
+NOTE: The API is only compatible with Python3.
+
+### Example functions
+See the Python API section of
+[Trace Processor (SQL)](/docs/analysis/trace-processor.md) to get
+more details on all available functions.
+
+#### Query
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+qr_it = tp.query('SELECT name FROM slice')
+for row in qr_it:
+  print(row.name)
+```
+**Output**
+```
+eglSwapBuffersWithDamageKHR
+onMessageReceived
+queueBuffer
+bufferLoad
+query
+...
+```
+#### Query as Pandas DataFrame
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+qr_it = tp.query('SELECT ts, name FROM slice')
+qr_df = qr_it.as_pandas_dataframe()
+print(qr_df.to_string())
+```
+**Output**
+```
+ts                   name
+-------------------- ---------------------------
+     261187017446933 eglSwapBuffersWithDamageKHR
+     261187017518340 onMessageReceived
+     261187020825163 queueBuffer
+     261187021345235 bufferLoad
+     261187121345235 query
+     ...
+```
+#### Metric
+```python
+from perfetto.trace_processor import TraceProcessor
+tp = TraceProcessor(file_path='trace.pftrace')
+
+cpu_metrics = tp.metric(['android_cpu'])
+print(cpu_metrics)
+```
+**Output**
+```
+metrics {
+  android_cpu {
+    process_info {
+      name: "/system/bin/init"
+      threads {
+        name: "init"
+        core {
+          id: 1
+          metrics {
+            mcycles: 1
+            runtime_ns: 570365
+            min_freq_khz: 1900800
+            max_freq_khz: 1900800
+            avg_freq_khz: 1902017
+          }
+        }
+        core {
+          id: 3
+          metrics {
+            mcycles: 0
+            runtime_ns: 366406
+            min_freq_khz: 1900800
+            max_freq_khz: 1900800
+            avg_freq_khz: 1902908
+          }
+        }
+        ...
+      }
+      ...
+    }
+    ...
+  }
+}
+```
+
 ## Next steps
 
 There are several options for exploring more of the trace analysis features
diff --git a/docs/reference/checkpoint-atoms.md b/docs/reference/checkpoint-atoms.md
new file mode 100644
index 0000000..a00ebfd
--- /dev/null
+++ b/docs/reference/checkpoint-atoms.md
@@ -0,0 +1,45 @@
+# Statsd Checkpoint Atoms
+## Tracing
+
+This diagram gives the atoms and the state transitions between when tracing/
+All atoms above log the UUID of the trace;
+`PERFETTO_TRACED_TRIGGER_STOP_TRACING` is special as it *also* logs the trigger
+name which caused trace finalization.
+
+NOTE: dotted lines indicate these transitions only happen in background
+configs; transitions with solid lines happen in both background and
+non-background cases.
+
+NOTE: for background traces, *either* start triggers or stop triggers are
+supported; both cannot happen for the same trace.
+
+```mermaid
+graph TD;
+    PERFETTO_CMD_TRACE_BEGIN-->PERFETTO_CMD_ON_CONNECT;
+    PERFETTO_CMD_BACKGROUND_TRACE_BEGIN-.->PERFETTO_CMD_ON_CONNECT
+    PERFETTO_CMD_ON_CONNECT-->PERFETTO_TRACED_ENABLE_TRACING
+    PERFETTO_TRACED_ENABLE_TRACING-->PERFETTO_TRACED_START_TRACING
+    PERFETTO_TRACED_ENABLE_TRACING-.->|start trigger background traces only|PERFETTO_TRACED_TRIGGER_START_TRACING
+    PERFETTO_TRACED_TRIGGER_START_TRACING-.->PERFETTO_TRACED_START_TRACING
+    PERFETTO_TRACED_START_TRACING-.->|stop trigger background traces only|PERFETTO_TRACED_TRIGGER_STOP_TRACING
+    PERFETTO_TRACED_TRIGGER_STOP_TRACING-.->PERFETTO_TRACED_DISABLE_TRACING
+    PERFETTO_TRACED_START_TRACING-->PERFETTO_TRACED_DISABLE_TRACING
+    PERFETTO_TRACED_DISABLE_TRACING-->PERFETTO_TRACED_NOTIFY_TRACING_DISABLED
+    PERFETTO_TRACED_NOTIFY_TRACING_DISABLED-->PERFETTO_CMD_ON_TRACING_DISABLED
+    PERFETTO_CMD_ON_TRACING_DISABLED-->PERFETTO_CMD_FINALIZE_TRACE_AND_EXIT
+    PERFETTO_CMD_FINALIZE_TRACE_AND_EXIT-->PERFETTO_CMD_UPLOAD_INCIDENT
+    PERFETTO_CMD_FINALIZE_TRACE_AND_EXIT-.->|only if no trigger happened|PERFETTO_CMD_NOT_UPLOADING_EMPTY_TRACE
+```
+
+## Triggers
+
+This diagram gives the atoms which can trigger finalization of a trace. 
+These atoms will not be reported individually but instead aggregated by trigger name
+and reported as a count.
+
+```mermaid
+graph TD;
+    PERFETTO_CMD_TRIGGER
+    PERFETTO_TRIGGER_PERFETTO_TRIGGER
+```
+
diff --git a/docs/reference/perfetto-cli.md b/docs/reference/perfetto-cli.md
index d101dee..f0771d6 100644
--- a/docs/reference/perfetto-cli.md
+++ b/docs/reference/perfetto-cli.md
@@ -37,7 +37,7 @@
 
 <pre class="none">
  adb shell perfetto [ --time <var>TIMESPEC</var> ] [ --buffer <var>SIZE</var> ] [ --size <var>SIZE</var> ]
-           [ <var>ATRACE_CAT</var> | <var>FTRACE_GROUP/FTRACE_NAME</var> | <var>FTRACE_GROUP/*</var> ]...
+           [ <var>ATRACE_CAT</var> | <var>FTRACE_GROUP/FTRACE_NAME</var>]...
 </pre>
 
 
@@ -57,7 +57,6 @@
 |--- |--- |
 |`ATRACE_CAT`|Specifies the atrace categories you want to record a trace for. For example, the following command traces Window Manager using atrace: `adb shell perfetto --out FILE wm`. To record other categories, see this [list of atrace categories](https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-q-preview-5/cmds/atrace/atrace.cpp#100).|
 |`FTRACE_GROUP/FTRACE_NAME`|Specifies the ftrace events you want to record a trace for. For example, the following command traces sched/sched_switch events: `adb shell perfetto --out FILE sched/sched_switch`|
-|`FTRACE_GROUP/*`|Record all events in group (e.g. sched/\*). Specifies the group of ftrace events you want to record a trace for. For example, the following command traces sched/\* events: `adb shell perfetto --out FILE 'sched/*'`|
 
 
 ## Normal mode
diff --git a/docs/toc.md b/docs/toc.md
index 03c2ad7..ac1500b 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -28,6 +28,7 @@
 * [App Instrumentation](#)
   * [Tracing SDK](instrumentation/tracing-sdk.md)
   * [Track events](instrumentation/track-events.md)
+  * [Interceptors](instrumentation/interceptors.md)
 
 * [Trace analysis](#)
   * [Trace Processor (SQL)](analysis/trace-processor.md)
@@ -35,6 +36,9 @@
   * [SQL tables](analysis/sql-tables.autogen)
   * [Stats table](analysis/sql-stats.autogen)
 
+* [Trace visualization](#)
+  * [Perfetto UI](visualization/perfetto-ui.md)
+
 * [Core concepts](#)
   * [Trace configuration](concepts/config.md)
   * [Buffers and dataflow](concepts/buffers.md)
@@ -55,6 +59,7 @@
     * [Common tasks](contributing/common-tasks.md)
     * [Embedding Perfetto](contributing/embedding.md)
     * [Releasing the SDK](contributing/sdk-releasing.md)
+    * [Chrome branches](contributing/chrome-branches.md)
 
 * [Design documents](#)
     * [API and ABI surface](design-docs/api-and-abi.md)
diff --git a/docs/visualization/perfetto-ui.md b/docs/visualization/perfetto-ui.md
new file mode 100644
index 0000000..f9a43f8
--- /dev/null
+++ b/docs/visualization/perfetto-ui.md
@@ -0,0 +1,73 @@
+# Perfetto UI
+
+[Perfetto UI](https://ui.perfetto.dev) enables you to view and analyze traces in
+the browser. It supports several different tracing formats, including the
+perfetto proto trace format and the legacy json trace format.
+
+## UI Tips and Tricks
+
+### Debug Slices
+
+Sometimes you may want to insert some fake slices into the timeline to help
+with your understanding of the data. You can do so by inserting rows into a
+magic `debug_slices` table.
+
+`debug_slices` table has five columns:
+
+* `id` (integer) [optional] If present, Perfetto UI will use it as slice id to
+  open the details panel when you click on the slices.
+* `name` (string) [optional] The displayed slice title.
+* `ts` (integer) [required] Start of the slice, in nanoseconds.
+* `dur` (integer) [required] Duration of the slice, in nanoseconds. Determines
+  slice width.
+* `depth` (integer) [optional] The row at which the slice is drawn. Depth 0 is
+  the first row.
+
+You can open the debug track by going to the "Metrics and auditors" menu on the
+left, and clicking "Show Debug Track". A debug slice track will become pinned to
+the top and will initially be empty. After you insert slices in the
+`debug_slices` table, you can click the reload button on the track to refresh
+the information shown in that track.
+
+Here is a simple example with random slices to illustrate the use:
+
+```sql
+CREATE VIEW rand_slices AS SELECT * FROM SLICE
+  ORDER BY RANDOM() LIMIT 2000;
+
+INSERT INTO debug_slices(id, name, ts, dur, depth)
+  SELECT id, name, ts, dur, depth FROM RAND_SLICES;
+```
+
+After you click the reload button, you should see the slices in the debug track.
+
+![Example of debug slices](/docs/images/debug-slices-random.png)
+
+Once you're done, you can click the X button to hide the track, and you can
+clear the `debug_slices` table (`DELETE FROM debug_slices`) to clear the track.
+
+A more interesting example is seeing RAIL modes in chrome traces:
+
+```sql
+SELECT RUN_METRIC('chrome/rail_modes.sql');
+
+-- Depth 0 is the unified RAIL Mode
+INSERT INTO debug_slices
+  SELECT NULL, rail_mode, ts, dur, 0 FROM combined_overall_rail_slices;
+
+-- Depth 2+ are for each Renderer process with depth 1 left blank
+INSERT INTO debug_slices
+  SELECT NULL, short_name, ts, dur, depth + 1 FROM rail_mode_slices,
+    (SELECT track_id, row_number() OVER () AS depth FROM
+      (SELECT DISTINCT track_id FROM rail_mode_slices)) depth_map,
+    rail_modes
+  WHERE depth_map.track_id = rail_mode_slices.track_id
+    AND rail_mode=rail_modes.mode;
+```
+
+This produces a visualization like this:
+
+![RAIL modes in Debug Track](/docs/images/rail-mode-debug-slices.png)
+
+Note: There is no equivalent debug counters feature yet, but the feature request
+is tracked on [b/168886909](http://b/168886909)).
diff --git a/examples/sdk/CMakeLists.txt b/examples/sdk/CMakeLists.txt
index fc13851..1e1c6e2 100644
--- a/examples/sdk/CMakeLists.txt
+++ b/examples/sdk/CMakeLists.txt
@@ -30,6 +30,7 @@
 add_executable(example_system_wide example_system_wide.cc
                trace_categories.cc)
 add_executable(example_custom_data_source example_custom_data_source.cc)
+add_executable(example_console example_console.cc trace_categories.cc)
 
 target_link_libraries(example perfetto
                       ${CMAKE_THREAD_LIBS_INIT})
@@ -37,10 +38,13 @@
                       ${CMAKE_THREAD_LIBS_INIT})
 target_link_libraries(example_custom_data_source perfetto
                       ${CMAKE_THREAD_LIBS_INIT})
+target_link_libraries(example_console perfetto
+                      ${CMAKE_THREAD_LIBS_INIT})
 
 # On Android we also need the logging library.
 if (ANDROID)
   target_link_libraries(example log)
   target_link_libraries(example_system_wide log)
   target_link_libraries(example_custom_data_source log)
+  target_link_libraries(example_console log)
 endif (ANDROID)
\ No newline at end of file
diff --git a/examples/sdk/example_console.cc b/examples/sdk/example_console.cc
new file mode 100644
index 0000000..e7a9879
--- /dev/null
+++ b/examples/sdk/example_console.cc
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+// This example shows how to log trace events to the console using the console
+// interceptor.
+
+#include "trace_categories.h"
+
+#include <chrono>
+#include <thread>
+
+void InitializePerfetto() {
+  perfetto::TracingInitArgs args;
+  args.backends = perfetto::kInProcessBackend;
+  perfetto::Tracing::Initialize(args);
+  perfetto::TrackEvent::Register();
+  perfetto::ConsoleInterceptor::Register();
+}
+
+std::unique_ptr<perfetto::TracingSession> StartTracing() {
+  perfetto::TraceConfig cfg;
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+
+  // Enable the console interceptor.
+  ds_cfg->mutable_interceptor_config()->set_name("console");
+
+  auto tracing_session = perfetto::Tracing::NewTrace();
+  tracing_session->Setup(cfg);
+  tracing_session->StartBlocking();
+  return tracing_session;
+}
+
+void DrawPlayer(int player_number) {
+  TRACE_EVENT("rendering", "DrawPlayer", "player_number", player_number);
+  // Sleep to simulate a long computation.
+  std::this_thread::sleep_for(std::chrono::milliseconds(500));
+}
+
+void DrawGame() {
+  // This is an example of an unscoped slice, which begins and ends at specific
+  // points (instead of at the end of the current block scope).
+  TRACE_EVENT_BEGIN("rendering", "DrawGame");
+  DrawPlayer(1);
+  DrawPlayer(2);
+  TRACE_EVENT_END("rendering");
+}
+
+int main(int, const char**) {
+  InitializePerfetto();
+  auto tracing_session = StartTracing();
+
+  // Simulate some work that emits trace events.
+  DrawGame();
+
+  tracing_session->StopBlocking();
+  return 0;
+}
diff --git a/examples/sdk/example_system_wide.cc b/examples/sdk/example_system_wide.cc
index f755d37..b7101de 100644
--- a/examples/sdk/example_system_wide.cc
+++ b/examples/sdk/example_system_wide.cc
@@ -19,9 +19,31 @@
 #include "trace_categories.h"
 
 #include <chrono>
+#include <condition_variable>
 #include <fstream>
 #include <thread>
 
+class Observer : public perfetto::TrackEventSessionObserver {
+ public:
+  Observer() { perfetto::TrackEvent::AddSessionObserver(this); }
+  ~Observer() { perfetto::TrackEvent::RemoveSessionObserver(this); }
+
+  void OnStart(const perfetto::DataSourceBase::StartArgs&) override {
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.notify_one();
+  }
+
+  void WaitForTracingStart() {
+    PERFETTO_LOG("Waiting for tracing to start...");
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [] { return perfetto::TrackEvent::IsEnabled(); });
+    PERFETTO_LOG("Tracing started");
+  }
+
+  std::mutex mutex;
+  std::condition_variable cv;
+};
+
 void InitializePerfetto() {
   perfetto::TracingInitArgs args;
   // The backends determine where trace events are recorded. For this example we
@@ -33,14 +55,6 @@
   perfetto::TrackEvent::Register();
 }
 
-void WaitForTracingStart() {
-  PERFETTO_LOG("Waiting for tracing to start...");
-  while (!TRACE_EVENT_CATEGORY_ENABLED("rendering")) {
-    std::this_thread::sleep_for(std::chrono::milliseconds(100));
-  }
-  PERFETTO_LOG("Tracing started");
-}
-
 void DrawPlayer(int player_number) {
   TRACE_EVENT("rendering", "DrawPlayer", "player_number", player_number);
   // Sleep to simulate a long computation.
@@ -55,7 +69,9 @@
 
 int main(int, const char**) {
   InitializePerfetto();
-  WaitForTracingStart();
+
+  Observer observer;
+  observer.WaitForTracingStart();
 
   // Simulate some work that emits trace events.
   // Note that we don't start and stop tracing here; for system-wide tracing
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 5049827..9c63051 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -55,14 +55,15 @@
   if (enable_perfetto_tools) {
     perfetto_local_symbolizer =
         "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || " +
-        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX()"
+        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||" +
+        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN()"
   } else {
     perfetto_local_symbolizer = "0"
   }
   if (enable_perfetto_trace_processor_httpd) {
     perfetto_tp_httpd = "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || " +
                         "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || " +
-                        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX()"
+                        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC()"
   } else {
     perfetto_tp_httpd = "0"
   }
@@ -102,6 +103,7 @@
 # All targets should depend on this target to inherit the right flags and
 # include directories.
 group("default_deps") {
+  visibility = [ "../*" ]  # Prevent chromium targets from depending on this (breaks component).
   public_configs = [ ":default_config" ]
   deps = [ ":gen_buildflags" ]
   if (perfetto_build_standalone) {
@@ -121,6 +123,7 @@
 # embedders that depend on perfetto (e.g. chrome). :public_config (see below) is
 # used for that.
 config("default_config") {
+  visibility = [ "../*" ]  # Prevent chromium targets from depending on this (breaks component).
   configs = [ ":public_config" ]
   defines = [ "PERFETTO_IMPLEMENTATION" ]
   include_dirs = [ ".." ]
@@ -194,7 +197,7 @@
 
 # Full protobuf is just for host tools .No binary shipped on device should
 # depend on this.
-whitelisted_protobuf_full_deps = [
+protobuf_full_deps_allowlist = [
   "../src/ipc/protoc_plugin:*",
   "../src/protozero/protoc_plugin:*",
   "../src/trace_processor:trace_processor_shell",
@@ -213,10 +216,9 @@
   libs = [ "protobuf" ]  # This will link against libprotobuf.so
 }
 
-# protoc compiler library, it's used for building protoc plugins and by
-# trace_processor_shell to dynamically load .proto files for metrics.
+# protoc compiler library, it's used for building protoc plugins.
 group("protoc_lib") {
-  visibility = whitelisted_protobuf_full_deps
+  visibility = protobuf_full_deps_allowlist
   if (current_toolchain == host_toolchain) {
     if (perfetto_use_system_protobuf) {
       public_configs = [
@@ -231,13 +233,11 @@
 }
 
 group("protobuf_full") {
-  visibility = whitelisted_protobuf_full_deps
-  if (current_toolchain == host_toolchain) {
-    if (perfetto_use_system_protobuf) {
-      public_configs = [ ":system_protobuf" ]
-    } else {
-      public_deps = [ "${perfetto_protobuf_target_prefix}:protobuf_full" ]
-    }
+  visibility = protobuf_full_deps_allowlist
+  if (perfetto_use_system_protobuf) {
+    public_configs = [ ":system_protobuf" ]
+  } else {
+    public_deps = [ "${perfetto_protobuf_target_prefix}:protobuf_full" ]
   }
 }
 
@@ -262,19 +262,33 @@
     "GOOGLE_PROTOBUF_NO_RTTI",
     "GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ]
-  cflags = [
-    "-Wno-unknown-warning-option",
-    "-Wno-deprecated",
-    "-Wno-undef",
-    "-Wno-zero-as-null-pointer-constant",
-  ]
+  cflags = []
+  if (is_clang || !is_win) {
+    cflags += [
+      "-Wno-unknown-warning-option",
+      "-Wno-deprecated",
+      "-Wno-undef",
+      "-Wno-zero-as-null-pointer-constant",
+    ]
+  }
+  if (is_clang && is_win) {
+    cflags += [
+      "-Wno-reserved-id-macro",
+      "-Wno-language-extension-token",
+      "-Wno-sign-conversion",
+      "-Wno-suggest-destructor-override",
+      "-Wno-undefined-reinterpret-cast",
+      "-Wno-inconsistent-missing-destructor-override",
+      "-Wno-unused-parameter",
+    ]
+  }
 
   if (!perfetto_use_system_protobuf) {
     cflags += [
       # Using -isystem instead of include_dirs (-I), so we don't need to
       # suppress warnings coming from libprotobuf headers. Doing so would mask
       # warnings in our own code.
-      "-isystem",
+      perfetto_isystem_cflag,
       rebase_path("../buildtools/protobuf/src", root_build_dir),
     ]
   }
diff --git a/gn/gen_perfetto_version_header.gni b/gn/gen_perfetto_version_header.gni
new file mode 100644
index 0000000..29e02e9
--- /dev/null
+++ b/gn/gen_perfetto_version_header.gni
@@ -0,0 +1,52 @@
+# 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.
+
+# This action generates a perfetto_version.gen.h which exposes some
+# PERFETTO_VERSION_xxx() macros that contains the git revision and release
+# number from the CHANGELOG.
+# This template is used only in two places: in //base (for C++ code) and in
+# //ui. This teamplate exists only to keep the logic consistent and avoid that
+# base's and ui's logic diverge.
+
+import("perfetto.gni")
+
+template("gen_perfetto_version_header") {
+  action(target_name) {
+    script = "${perfetto_root_path}tools/write_version_header.py"
+    changelog = "${perfetto_root_path}CHANGELOG"
+    inputs = [ changelog ]
+    outputs = []
+    args = []
+    if (perfetto_build_standalone && !is_perfetto_build_generator) {
+      inputs += [ "${perfetto_root_path}.git/HEAD" ]
+    }
+
+    if (defined(invoker.cpp_out)) {
+      args += [
+        "--changelog",
+        rebase_path(changelog, root_build_dir),
+        "--cpp_out",
+        rebase_path(invoker.cpp_out, root_build_dir),
+      ]
+      outputs += [ invoker.cpp_out ]
+    }
+    if (defined(invoker.ts_out)) {
+      args += [
+        "--ts_out",
+        rebase_path(invoker.ts_out, root_build_dir),
+      ]
+      outputs += [ invoker.ts_out ]
+    }
+  }
+}
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index dd79c2a..5b4ad49 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -133,7 +133,7 @@
 declare_args() {
   # Platform-wide tracing executables (traced, traced_probes, perfetto_cmd).
   enable_perfetto_platform_services =
-      perfetto_build_standalone || perfetto_build_with_android
+      (perfetto_build_standalone && !is_win) || perfetto_build_with_android
 
   # Allow the embedder to use the IPC layer. In turn this allows to use the
   # system backend in the client library.
@@ -181,18 +181,19 @@
                               build_with_chromium || perfetto_build_with_android
 
   enable_perfetto_integration_tests =
-      perfetto_build_standalone || perfetto_build_with_android
+      (perfetto_build_standalone && !is_win) || perfetto_build_with_android
 
-  enable_perfetto_benchmarks = perfetto_build_standalone
+  enable_perfetto_benchmarks = perfetto_build_standalone && !is_win
 
   enable_perfetto_fuzzers =
       perfetto_build_standalone && defined(is_fuzzer) && is_fuzzer
 
-  # Enables the gen_git_revision tool that generates a .h that contains a macro
-  # with the current git revision. Works only in standalone GN checkouts.
-  # If disabled, the version string will be "unknown".
+  # Enables the write_version_header.py tool that generates a .h that contains a
+  # macro with the current git revision and latest release version from
+  # CHANGELOG. If false base/version.h will return "unknown".
   enable_perfetto_version_gen =
-      perfetto_build_standalone && !is_perfetto_build_generator
+      perfetto_build_standalone || is_perfetto_build_generator ||
+      perfetto_build_with_android
 
   # Only for local development. When true the binaries (perfetto, traced, ...)
   # are monolithic and don't use a common shared library. This is mainly to
@@ -208,7 +209,7 @@
   # the stack and prints the stack trace on stderr. Requires a dependency on
   # libbacktrace when enabled.
   enable_perfetto_stderr_crash_dump =
-      is_debug && perfetto_build_standalone && !is_wasm
+      is_debug && perfetto_build_standalone && !is_wasm && !is_win
 }
 
 declare_args() {
@@ -238,7 +239,7 @@
   # Enables httpd RPC support in the trace processor.
   # Further per-OS conditionals are applied in gn/BUILD.gn.
   enable_perfetto_trace_processor_httpd =
-      enable_perfetto_trace_processor && perfetto_build_standalone
+      enable_perfetto_trace_processor && perfetto_build_standalone && !is_win
 
   # Enables Zlib support. This is used both by the "perfetto" cmdline client
   # (for compressing traces) and by trace processor (for compressed traces).
@@ -253,7 +254,8 @@
 
   # Allows to build the UI (TypeScript/ HTML / WASM)
   enable_perfetto_ui =
-      perfetto_build_standalone && enable_perfetto_trace_processor_sqlite
+      perfetto_build_standalone && enable_perfetto_trace_processor_sqlite &&
+      host_os != "win"
 
   # Skip buildtools dependency checks (needed for ChromeOS).
   skip_buildtools_check = false
@@ -263,6 +265,13 @@
   perfetto_use_system_protobuf = false
 }
 
+if (is_win) {
+  # clang-cl
+  perfetto_isystem_cflag = "/I"
+} else {
+  perfetto_isystem_cflag = "-isystem"
+}
+
 # +---------------------------------------------------------------------------+
 # | Cross-checks                                                              |
 # +---------------------------------------------------------------------------+
diff --git a/gn/perfetto_benchmarks.gni b/gn/perfetto_benchmarks.gni
index ca2e36a..ddca3f2 100644
--- a/gn/perfetto_benchmarks.gni
+++ b/gn/perfetto_benchmarks.gni
@@ -21,7 +21,7 @@
   "src/trace_processor/sqlite:benchmarks",
   "src/trace_processor/containers:benchmarks",
   "src/trace_processor/tables:benchmarks",
-  "src/traced/probes/ftrace/kallsyms:benchmarks",
+  "src/kallsyms:benchmarks",
   "src/traced/probes/ftrace:benchmarks",
   "src/tracing/core:benchmarks",
   "src/tracing:benchmarks",
diff --git a/gn/perfetto_component.gni b/gn/perfetto_component.gni
new file mode 100644
index 0000000..3c40d5d
--- /dev/null
+++ b/gn/perfetto_component.gni
@@ -0,0 +1,58 @@
+# 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.
+
+import("perfetto.gni")
+
+# This template is used when generating build files via tools/gen_android_bp
+# and tools/gen_bazel. This is to solve the problem that other build systems
+# don't have a similar concept to GN's source_set and dependencies can only
+# happen between static libraries (or shared, but we don't use them).
+
+# In future this could be used for chromium component builds, where each
+# component becomes its own shared library (see b/159411946). This alone isn't
+# enough for that use case as it will require splitting also the various
+# export.h files.
+
+# TODO(primiano): we cannot split components as static libraries in Android
+# because heapprofd_client rebuilds base with
+# -DPERFETTO_ANDROID_ASYNC_SAFE_LOG. Once this is fixed re-enable the
+# ODRChecker in tools/gen_android_bp.
+
+# The condition below really means: "is Bazel generator".
+if (is_perfetto_build_generator && !perfetto_build_with_android) {
+  perfetto_component_type = "static_library"
+} else {
+  perfetto_component_type = "source_set"
+}
+
+template("perfetto_component") {
+  target(perfetto_component_type, target_name) {
+    forward_variables_from(invoker, "*")
+    if (perfetto_component_type == "static_library") {
+      # Mangle the name of the library putting the full path in it. In component
+      # builds we don't care about file names, as nobody depends from the
+      # outside on the internal component libraries.
+      # This is because library targets are stored in the root output folder
+      # (not in a subfolder that matches their path). This mangling avoid file
+      # avoid name clashes when the target is called "common" or similar, so we
+      # use src_ipc_common.a rather than common.a.
+      _name = get_label_info(target_name, "label_no_toolchain")
+      _name = string_replace(_name, "//", "")
+      _name = string_replace(_name, "/", "_")
+      _name = string_replace(_name, ":", "_")
+      output_name = _name
+      complete_static_lib = true
+    }
+  }
+}
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index 41fcd00..9b6a95d 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -53,7 +53,7 @@
     "src/traced/probes:unittests",
     "src/traced/probes/filesystem:unittests",
     "src/traced/probes/ftrace:unittests",
-    "src/traced/probes/ftrace/kallsyms:unittests",
+    "src/kallsyms:unittests",
     "src/traced/service:unittests",
   ]
 }
diff --git a/gn/proto_library.gni b/gn/proto_library.gni
index ca5370b..a52982b 100644
--- a/gn/proto_library.gni
+++ b/gn/proto_library.gni
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("perfetto.gni")
+import("perfetto_component.gni")
 
 # This gni file defines rules for proto generation. There are various types of
 # proto targets that can be defined in our codebase:
@@ -51,6 +52,13 @@
     perfetto_protobuf_gni = "//third_party/protobuf/proto_library.gni"
   }
 }
+if (!defined(perfetto_protobuf_src_dir)) {
+  if (perfetto_root_path == "//") {
+    perfetto_protobuf_src_dir = "//buildtools/protobuf/src"
+  } else {
+    perfetto_protobuf_src_dir = "//third_party/protobuf/src"
+  }
+}
 import(perfetto_protobuf_gni)
 
 # Equivalent to proto_library (generation of .h/.cc from .proto files) but
@@ -77,7 +85,16 @@
       deps = []
     }
 
-    deps += [ perfetto_root_path + "src/protozero" ]
+    # omit_protozero_dep is intended to be used when protozero_library
+    # is used in Chrome (for generation of code for proto extensions)
+    # to avoid ODR violations in case of component builds. The embedder
+    # (Chrome) is then responsible for adding the appropriate transitive
+    # dependency on Protozero.
+    #
+    # TODO(b/173041866): use fine-grained components instead when available
+    if (!(defined(invoker.omit_protozero_dep) && invoker.omit_protozero_dep)) {
+      deps += [ perfetto_root_path + "src/protozero" ]
+    }
 
     forward_variables_from(invoker,
                            [
@@ -91,6 +108,7 @@
                              "visibility",
                              "generate_descriptor",
                              "propagate_imports_configs",
+                             "import_dirs",
                            ])
   }
 }
@@ -135,6 +153,7 @@
                              "visibility",
                              "generate_descriptor",
                              "propagate_imports_configs",
+                             "import_dirs",
                            ])
   }
 }
@@ -148,10 +167,12 @@
     generator_plugin_label =
         "$perfetto_root_path/src/ipc/protoc_plugin:ipc_plugin"
     generator_plugin_suffix = ".ipc"
-    deps = [
-      "$perfetto_root_path/gn:default_deps",
-      "$perfetto_root_path/src/ipc:common",
-    ]
+    deps = [ "$perfetto_root_path/gn:default_deps" ]
+    if (perfetto_component_type == "static_library") {
+      deps += [ "$perfetto_root_path/src/ipc:perfetto_ipc" ]
+    } else {
+      deps += [ "$perfetto_root_path/src/ipc:common" ]
+    }
     if (defined(invoker.deps)) {
       deps += invoker.deps
     }
@@ -167,6 +188,7 @@
                              "testonly",
                              "visibility",
                              "propagate_imports_configs",
+                             "import_dirs",
                            ])
   }
 }
@@ -180,6 +202,7 @@
       "zero",
       "lite",
       "cpp",
+      "source_set",
     ]
   }
 
@@ -190,6 +213,12 @@
     proto_path = perfetto_root_path
   }
 
+  if (defined(invoker.import_dirs)) {
+    import_dirs_ = invoker.import_dirs
+  } else {
+    import_dirs_ = []
+  }
+
   vars_to_forward = [
     "sources",
     "visibility",
@@ -220,6 +249,7 @@
         generator_plugin_options = "wrapper_namespace=pbzero"
         deps = deps_
         propagate_imports_configs = propagate_imports_configs_
+        import_dirs = import_dirs_
         forward_variables_from(invoker, vars_to_forward)
       }
     } else if (gen_type == "cpp") {
@@ -229,6 +259,7 @@
         generator_plugin_options = "wrapper_namespace=gen"
         deps = deps_
         propagate_imports_configs = propagate_imports_configs_
+        import_dirs = import_dirs_
         forward_variables_from(invoker, vars_to_forward)
       }
     } else if (gen_type == "ipc") {
@@ -239,6 +270,7 @@
         generator_plugin_options = "wrapper_namespace=gen"
         deps = deps_ + [ ":$cpp_target_name_" ]
         propagate_imports_configs = propagate_imports_configs_
+        import_dirs = import_dirs_
         forward_variables_from(invoker, vars_to_forward)
       }
     } else if (gen_type == "lite") {
@@ -249,6 +281,7 @@
         deps = deps_
         cc_generator_options = "lite=true:"
         propagate_imports_configs = propagate_imports_configs_
+        import_dirs = import_dirs_
         forward_variables_from(invoker, vars_to_forward)
       }
     } else if (gen_type == "descriptor") {
@@ -257,13 +290,41 @@
         proto_out_dir = proto_path
         generate_python = false
         generate_cc = false
-        generate_descriptor = invoker.generate_descriptor
+        generate_descriptor =
+            rebase_path(invoker.generate_descriptor, proto_path)
         deps = deps_
+        import_dirs = import_dirs_
         forward_variables_from(invoker, vars_to_forward)
       }
 
       # Not needed for descriptor proto_library target.
       not_needed([ "propagate_imports_configs_" ])
+    } else if (gen_type == "source_set") {
+      action(target_name_) {
+        out_path = "$target_gen_dir/" + target_name_
+        rebased_out_path =
+            rebase_path(target_gen_dir, root_build_dir) + "/" + target_name_
+
+        script = "$perfetto_root_path/tools/touch_file.py"
+        args = [
+          "--output",
+          rebased_out_path,
+        ]
+        outputs = [ out_path ]
+        deps = deps_
+
+        metadata = {
+          proto_library_sources = invoker.sources
+          import_dirs = import_dirs_
+        }
+        forward_variables_from(invoker, vars_to_forward)
+      }
+
+      # Not needed for source_set proto_library target.
+      not_needed([
+                   "propagate_imports_configs_",
+                   "proto_path",
+                 ])
     } else {
       assert(false, "Invalid 'proto_generators' value.")
     }
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index b7916b7..6da256e 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -14,67 +14,123 @@
 
 import("//gn/perfetto_check_build_deps.gni")
 import("//gn/standalone/android.gni")
+import("//gn/standalone/libc++/libc++.gni")
 import("//gn/standalone/sanitizers/sanitizers.gni")
+import("//gn/standalone/toolchain/msvc.gni")
 import("//gn/standalone/wasm.gni")
 
 # These warnings have been introduced with the newest version of clang (only in
 # the hermetic build) and are enabled just with -Werror.
 hermetic_clang_suppressions = [ "-Wno-c99-designator" ]
 
+# We deal with three toolchains here:
+# 1. Clang, used in most cases.
+# 2. GCC, used in some Linux cases.
+# 3. MSVC, used in some Windows cases.
+# Clang vs gcc is typically not a problem: both support roughly the same
+# switches. -Wno-unknown-warning-option fixes the mismatching ones.
+# The situation on Windows is a bit trickier: clang-cl.exe really pretends to be
+# cl.exe (MSVC), so we should use MSVC-style switches (e.g. /W2). However,
+# clang-cl.exe still supports some -Wclang-style-switches for flags that don't
+# have a corresponding version in MSVC.
+#
+# In the rules below, the conditionals should be interpreted as follows:
+# is_win -> can be either clang-cl.exe or cl.exe (MSVC). Only MSVC-style
+#           switches (the common denominator) should be used.
+# is_clang -> could be clang-on-linux, clang-on-mac or clang-cl.exe.
+
 config("extra_warnings") {
-  cflags = [
-    "-Wall",
-    "-Wextra",
-    "-Wpedantic",
-  ]
+  if (is_win) {
+    cflags = [
+      "/W2",
+      "/wd4244",  # conversion from 'float' to 'int', possible loss of data
+      "/wd4267",  # conversion from 'size_t' to 'int', possible loss of data
+    ]
+    if (is_clang) {
+      cflags += [
+        "-Wno-float-equal",
+        "-Wno-unused-macros",
+        "-Wno-old-style-cast",
+      ]
+    }
+  } else {
+    # Clang or Gcc. On linux, Android and Mac.
+    cflags = [
+      "-Wall",
+      "-Wextra",
+      "-Wpedantic",
+    ]
+  }
 
   # Disable variadic macro warning as we make extensive use of them in trace
   # processor and client API.
   if (is_clang) {
-    cflags += [ "-Wno-gnu-zero-variadic-macro-arguments" ]
-  }
-
-  # Disable Weverything on fuzzers to avoid breakages when new versions of clang
-  # are rolled into OSS-fuzz.
-  if (is_clang && !is_fuzzer) {
+    if (!is_fuzzer) {
+      # Disable Weverything on fuzzers to avoid breakages when new versions of
+      # clang are rolled into OSS-fuzz.
+      cflags += [ "-Weverything" ]
+    }
     cflags += [
-      "-Weverything",
       "-Wno-c++98-compat-pedantic",
       "-Wno-c++98-compat",
       "-Wno-disabled-macro-expansion",
+      "-Wno-documentation-unknown-command",
       "-Wno-gnu-include-next",
       "-Wno-gnu-statement-expression",
+      "-Wno-gnu-zero-variadic-macro-arguments",
       "-Wno-padded",
+      "-Wno-poison-system-directories",
       "-Wno-reserved-id-macro",
       "-Wno-unknown-sanitizers",
+      "-Wno-unknown-warning-option",
     ]
-  }
-
-  if (!is_clang) {
-    # Use return std::move(...) for compatibility with old compilers.
+  } else if (!is_clang && !is_win) {
+    # Use return std::move(...) for compatibility with old GCC compilers.
     cflags += [ "-Wno-redundant-move" ]
   }
 }
 
 config("no_exceptions") {
-  cflags_cc = [ "-fno-exceptions" ]
+  # Exceptions are disabled by default on Windows (Use /EHsc to enable them).
+  if (!is_win) {
+    cflags_cc = [ "-fno-exceptions" ]
+  }
 }
 
 config("no_rtti") {
-  cflags_cc = [ "-fno-rtti" ]
+  if (is_win) {
+    cflags_cc = [ "/GR-" ]
+  } else {
+    cflags_cc = [ "-fno-rtti" ]
+  }
 }
 
 config("c++11") {
-  cflags_cc = [ "-std=c++11" ]
+  # C++11 is the default on Windows.
+  if (!is_win) {
+    cflags_cc = [ "-std=c++11" ]
+  }
 }
 
 # This is needed to compile libunwindstack.
 config("c++17") {
-  cflags_cc = [ "-std=c++17" ]
+  if (is_win) {
+    cflags_cc = [ "/std:c++17" ]
+  } else {
+    cflags_cc = [ "-std=c++17" ]
+  }
 }
 
 config("visibility_hidden") {
-  cflags = [ "-fvisibility=hidden" ]
+  if (!is_win) {
+    cflags = [ "-fvisibility=hidden" ]
+  }
+}
+
+config("win32_lean_and_mean") {
+  if (is_win) {
+    defines = [ "WIN32_LEAN_AND_MEAN" ]
+  }
 }
 
 config("default") {
@@ -83,27 +139,58 @@
   cflags_c = []
   cflags_cc = []
   defines = []
+  include_dirs = []
   ldflags = []
   libs = []
 
-  cflags += [
-    "-fstrict-aliasing",
-    "-fstack-protector-strong",
-    "-fPIC",
-    "-g",
-    "-Wformat",
-  ]
+  if (is_android || is_linux) {
+    ldflags += [ "-Wl,--build-id" ]
+  }
 
+  if (is_clang || !is_win) {  # Clang or GCC, but not MSVC.
+    cflags += [
+      "-fstrict-aliasing",
+      "-Wformat",
+    ]
+  }
+
+  if (is_win) {
+    cflags += [
+      "/bigobj",  # Some of our files are bigger than the regular limits.
+      "/Gy",  # Enable function-level linking.
+    ]
+    defines += [
+      "_CRT_NONSTDC_NO_WARNINGS",
+      "_CRT_SECURE_NO_DEPRECATE",
+      "_CRT_SECURE_NO_WARNINGS",  # Disables warnings on some POSIX-compat API.
+      "_SCL_SECURE_NO_DEPRECATE",
+      "NOMINMAX",
+    ]
+    if (!use_custom_libcxx) {
+      defines += [ "_HAS_EXCEPTIONS=0" ]  # Disables exceptions in MSVC STL.
+    }
+  } else {  # !is_win
+    cflags += [
+      "-g",
+      "-fPIC",
+      "-fstack-protector-strong",
+    ]
+  }
+
+  # Treat warnings as errors, but give up on fuzzer builds.
   if (!is_fuzzer) {
-    cflags += [ "-Werror" ]
+    if (is_win) {
+      cflags += [ "/WX" ]
+    } else {
+      cflags += [ "-Werror" ]
+    }
   }
 
   if (is_clang) {
-    cflags += [
-      # Color compiler output, see https://github.com/ninja-build/ninja/wiki/FAQ
-      "-fcolor-diagnostics",
-      "-fdiagnostics-show-template-tree",
-    ]
+    cflags += [ "-fcolor-diagnostics" ]
+    if (!is_win) {
+      cflags += [ "-fdiagnostics-show-template-tree" ]
+    }
   }
 
   if (is_hermetic_clang && is_linux && !is_wasm) {
@@ -117,6 +204,9 @@
     ldflags += [ "-flto=full" ]
   }
 
+  # We support only x64 builds on Windows.
+  assert(!is_win || current_cpu == "x64")
+
   if (current_cpu == "arm") {
     cflags += [
       "-march=armv7-a",
@@ -145,8 +235,24 @@
     ]
   }
 
+  if (is_win && !is_clang) {
+    # When using MSVC we need to manually pass the include dirs. clang-cl.exe
+    # doesn't need them because it's smart enough to figure out the right path
+    # by querying the registry on its own.
+    include_dirs = win_msvc_inc_dirs  # Defined in msvc.gni.
+  }
+
   if (is_debug) {
-    libs += [ "dl" ]
+    if (is_win) {
+      cflags += [ "/Z7" ]
+      if (is_clang) {
+        # Required to see symbols in windbg when building with clang-cl.exe.
+        cflags += [ "-gcodeview-ghash" ]
+        ldflags = [ "/DEBUG:GHASH" ]
+      }
+    } else {
+      libs += [ "dl" ]
+    }
   }
 
   if (is_android) {
@@ -177,7 +283,6 @@
       "-Wl,--exclude-libs,libunwind.a",
       "-Wl,--exclude-libs,libgcc.a",
       "-Wl,--exclude-libs,libc++_static.a",
-      "-Wl,--build-id",
       "-Wl,--no-undefined",
       "-Wl,-z,noexecstack",
       "-Wl,-z,relro",
@@ -202,27 +307,49 @@
 }
 
 config("debug_symbols") {
-  cflags = [ "-O0" ]
+  cflags = []
+  if (is_win) {
+    cflags = [ "/Od" ]
+  } else {
+    cflags = [ "-O0" ]
+  }
   if (is_android || is_linux) {
     cflags += [ "-funwind-tables" ]
   }
 }
 
 config("release") {
-  cflags = [
-    "-fdata-sections",
-    "-ffunction-sections",
-  ]
-  if (is_android) {
-    cflags += [ "-O2" ]
+  # Compiler flags for release builds.
+  if (is_win) {
+    cflags = [
+      "/O2",
+      "/Zc:inline",
+    ]
+  } else if (is_android) {
+    cflags = [ "-O2" ]
   } else if (is_fuzzer) {
-    cflags += [ "-O1" ]
+    cflags = [ "-O1" ]
   } else {
-    cflags += [ "-O3" ]
+    cflags = [ "-O3" ]
   }
-  if (is_mac) {
+  if (!is_win) {
+    cflags += [
+      "-fdata-sections",
+      "-ffunction-sections",
+    ]
+  }
+
+  # Linker flags for release builds.
+  if (is_win) {
+    ldflags = [
+      "/OPT:REF",
+      "/OPT:ICF",
+      "/INCREMENTAL:NO",
+      "/FIXED:NO",
+    ]
+  } else if (is_mac) {
     ldflags = [ "-dead_strip" ]
-  } else {
+  } else if (!is_win) {
     ldflags = [
       "-Wl,--gc-sections",
       "-Wl,--icf=all",
@@ -272,22 +399,6 @@
   }
 }
 
-config("gen_include_path") {
-  include_dirs = [ root_gen_dir ]
-}
-
-# This action generates a perfetto_version.gen.h which contains the git SHA1
-# of HEAD. The file can be included from C/C++ sources and exposes a
-# PERFETTO_GET_GIT_REVISION() macro that contains the git revision.
-action("gen_git_revision") {
-  script = "gen_git_revision.py"
-  generated_header = "${root_gen_dir}/perfetto_version.gen.h"
-  args = [ rebase_path(generated_header, root_build_dir) ]
-  inputs = []
-  outputs = [ generated_header ]
-  public_configs = [ ":gen_include_path" ]
-}
-
 # Checks that tools/install-build-deps has been run since it last changed.
 perfetto_check_build_deps("check_build_deps") {
   args = []
diff --git a/gn/standalone/BUILDCONFIG.gn b/gn/standalone/BUILDCONFIG.gn
index aa0c2bc..df61ef1 100644
--- a/gn/standalone/BUILDCONFIG.gn
+++ b/gn/standalone/BUILDCONFIG.gn
@@ -35,10 +35,12 @@
 is_linux = current_os == "linux"
 is_linux_host = host_os == "linux"
 is_mac = current_os == "mac"
+is_mac_host = host_os == "mac"
+is_win = current_os == "win"
+is_win_host = host_os == "win"
 
 # Building with Windows/Fuchsia/nacl is currently only supported in the Chromium
 # tree so always set this to false.
-is_win = false
 is_fuchsia = false
 is_nacl = false
 
@@ -67,6 +69,10 @@
   "//gn/standalone/sanitizers:sanitizers_cflags",
 ]
 
+if (is_win) {
+  default_configs += [ "//gn/standalone:win32_lean_and_mean" ]
+}
+
 if (!is_debug) {
   default_configs -= [ "//gn/standalone:debug_symbols" ]
   default_configs += [ "//gn/standalone:release" ]
@@ -99,7 +105,11 @@
   configs += [ "//gn/standalone:android_liblog" ]
 }
 
-_default_toolchain = "//gn/standalone/toolchain:gcc_like"
+if (is_win) {
+  _default_toolchain = "//gn/standalone/toolchain:msvc"
+} else {
+  _default_toolchain = "//gn/standalone/toolchain:gcc_like"
+}
 set_default_toolchain(_default_toolchain)
 
 if (is_cross_compiling) {
diff --git a/gn/standalone/android.gni b/gn/standalone/android.gni
index d798b37..31a4d22 100644
--- a/gn/standalone/android.gni
+++ b/gn/standalone/android.gni
@@ -22,7 +22,7 @@
   } else if (host_os == "mac") {
     android_host = "darwin-x86_64"
   } else {
-    assert(false, "Need Android toolchain support for your build OS.")
+    android_host = "UNSUPPORTED_ON_WINDOWS"
   }
 }
 
diff --git a/gn/standalone/cp.py b/gn/standalone/cp.py
new file mode 100644
index 0000000..8a3ef0d
--- /dev/null
+++ b/gn/standalone/cp.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# 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.
+
+import os
+import shutil
+import sys
+
+
+def main():
+  src, dst = sys.argv[1:]
+
+  if os.path.exists(dst):
+    if os.path.isdir(dst):
+      shutil.rmtree(dst)
+    else:
+      os.remove(dst)
+
+  if os.path.isdir(src):
+    shutil.copytree(src, dst)
+  else:
+    shutil.copy2(src, dst)
+    #work around https://github.com/ninja-build/ninja/issues/1554
+    os.utime(dst, None)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/gn/standalone/fuzzer.gni b/gn/standalone/fuzzer.gni
index 0b3f332..cac571a 100644
--- a/gn/standalone/fuzzer.gni
+++ b/gn/standalone/fuzzer.gni
@@ -15,10 +15,9 @@
 import("//gn/standalone/sanitizers/sanitizers.gni")
 
 template("perfetto_fuzzer_test") {
-  forward_variables_from(invoker, "*")
-
   if (is_fuzzer) {
     executable(target_name) {
+      forward_variables_from(invoker, "*")
       if (use_libfuzzer) {
         deps += [ "//gn:libfuzzer" ]
       } else {
@@ -27,7 +26,7 @@
     }
   } else {
     not_needed(invoker, "*")
-    source_set(target_name) {
+    group(target_name) {
     }
   }
 }  # template
diff --git a/gn/standalone/gen_git_revision.py b/gn/standalone/gen_git_revision.py
deleted file mode 100755
index a45262a..0000000
--- a/gn/standalone/gen_git_revision.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import subprocess
-import sys
-
-
-def main(argv):
-  if len(argv) != 2:
-    print('Usage: %s output_file.h' % argv[0])
-    return 1
-  script_dir = os.path.dirname(os.path.realpath(__file__))
-  revision = subprocess.check_output(
-      ['git', '-C', script_dir, 'rev-parse', 'HEAD']).strip()
-  new_contents = '#define PERFETTO_GET_GIT_REVISION() "%s"\n' % revision
-  out_file = argv[1]
-  old_contents = ''
-  if os.path.isfile(out_file):
-    with open(out_file) as f:
-      old_contents = f.read()
-  if old_contents == new_contents:
-    return 0
-  with open(out_file, 'w') as f:
-    f.write(new_contents)
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv))
diff --git a/gn/standalone/proto_library.gni b/gn/standalone/proto_library.gni
index 95948a3..4df58fb 100644
--- a/gn/standalone/proto_library.gni
+++ b/gn/standalone/proto_library.gni
@@ -14,6 +14,12 @@
 
 import("../perfetto.gni")
 
+if (host_os == "win") {
+  _host_executable_suffix = ".exe"
+} else {
+  _host_executable_suffix = ""
+}
+
 template("proto_library") {
   assert(defined(invoker.sources))
   proto_sources = invoker.sources
@@ -33,6 +39,11 @@
   # generate_python = true.
   assert(defined(invoker.generate_python) && !invoker.generate_python)
 
+  import_dirs = []
+  if (defined(invoker.import_dirs)) {
+    import_dirs = invoker.import_dirs
+  }
+
   # If false will not generate the default .pb.{cc,h} files. Used for custom
   # codegen plugins.
   generate_cc = true
@@ -47,8 +58,9 @@
 
   if (defined(invoker.generator_plugin_label)) {
     plugin_host_label = invoker.generator_plugin_label + "($host_toolchain)"
-    plugin_path = get_label_info(plugin_host_label, "root_out_dir") + "/" +
-                  get_label_info(plugin_host_label, "name")
+    plugin_path =
+        get_label_info(plugin_host_label, "root_out_dir") + "/" +
+        get_label_info(plugin_host_label, "name") + _host_executable_suffix
     generate_with_plugin = true
   } else if (defined(invoker.generator_plugin_script)) {
     plugin_path = invoker.generator_plugin_script
@@ -68,19 +80,19 @@
     }
   }
 
-  cc_out_dir = "$root_gen_dir/" + proto_out_dir
-  rel_cc_out_dir = rebase_path(cc_out_dir, root_build_dir)
+  out_dir = "$root_gen_dir/" + proto_out_dir
+  rel_out_dir = rebase_path(out_dir, root_build_dir)
 
   # Prevent unused errors when generating descriptor only.
   if (generate_descriptor != "") {
-    not_needed([ "rel_cc_out_dir" ])
+    not_needed([ "rel_out_dir" ])
   }
 
   protos = rebase_path(proto_sources, proto_in_dir)
   protogens = []
 
   if (generate_descriptor != "") {
-    protogens += [ "$target_gen_dir/" + generate_descriptor ]
+    protogens += [ "$out_dir/${generate_descriptor}" ]
   }
 
   foreach(proto, protos) {
@@ -95,13 +107,13 @@
 
     if (generate_cc) {
       protogens += [
-        "$cc_out_dir/$proto_path.pb.h",
-        "$cc_out_dir/$proto_path.pb.cc",
+        "$out_dir/$proto_path.pb.h",
+        "$out_dir/$proto_path.pb.cc",
       ]
     }
     if (generate_with_plugin) {
       foreach(suffix, generator_plugin_suffixes) {
-        protogens += [ "$cc_out_dir/${proto_path}${suffix}" ]
+        protogens += [ "$out_dir/${proto_path}${suffix}" ]
       }
     }
   }
@@ -115,7 +127,7 @@
   }
 
   config(config_name) {
-    include_dirs = [ cc_out_dir ]
+    include_dirs = [ out_dir ]
   }
 
   # The XXX_gen action that generates the .pb.{cc,h} files.
@@ -123,28 +135,33 @@
     if (generate_descriptor == "") {
       visibility = [ ":$source_set_name" ]
     }
-    script = "//gn/standalone/build_tool_wrapper.py"
     sources = proto_sources
     outputs = get_path_info(protogens, "abspath")
 
-    protoc_script = "//gn/standalone/protoc.py"
-
     if (perfetto_use_system_protobuf) {
-      protoc_rebased_path = "protoc"  # from PATH
+      protoc_rebased_path = "protoc" + _host_executable_suffix  # from PATH
     } else {
       protoc_label = "//gn:protoc($host_toolchain)"
-      protoc_path = get_label_info(protoc_label, "root_out_dir") + "/protoc"
+      protoc_path = get_label_info(protoc_label, "root_out_dir") + "/protoc" +
+                    _host_executable_suffix
       protoc_rebased_path = "./" + rebase_path(protoc_path, root_build_dir)
     }
+    script = "//gn/standalone/protoc.py"
     args = [
-      "./" + rebase_path(protoc_script, root_build_dir),
-
       # Path should be rebased because |root_build_dir| for current toolchain
       # may be different from |root_out_dir| of protoc built on host toolchain.
       protoc_rebased_path,
       "--proto_path",
       rebase_path(proto_in_dir, root_build_dir),
     ]
+
+    foreach(path, import_dirs) {
+      args += [
+        "--proto_path",
+        rebase_path(path, root_build_dir),
+      ]
+    }
+
     if (generate_cc) {
       cc_generator_options_ = ""
       if (defined(invoker.cc_generator_options)) {
@@ -152,15 +169,15 @@
       }
       args += [
         "--cpp_out",
-        cc_generator_options_ + rel_cc_out_dir,
+        cc_generator_options_ + rel_out_dir,
       ]
     }
     if (generate_descriptor != "") {
-      depfile = "$target_gen_dir/$generate_descriptor.d"
+      depfile = "$root_gen_dir/$generate_descriptor.d"
       args += [
         "--include_imports",
         "--descriptor_set_out",
-        rebase_path("$target_gen_dir/$generate_descriptor", root_build_dir),
+        rebase_path("$root_gen_dir/$generate_descriptor", root_build_dir),
         "--dependency_out",
         rebase_path(depfile, root_build_dir),
       ]
@@ -172,7 +189,7 @@
       if (defined(invoker.generator_plugin_options)) {
         plugin_out_args += invoker.generator_plugin_options
       }
-      plugin_out_args += ":$rel_cc_out_dir"
+      plugin_out_args += ":$rel_out_dir"
 
       args += [
         "--plugin=protoc-gen-plugin=$plugin_path_rebased",
diff --git a/gn/standalone/protoc.py b/gn/standalone/protoc.py
index 723c35e..4d8c47d 100755
--- a/gn/standalone/protoc.py
+++ b/gn/standalone/protoc.py
@@ -23,6 +23,7 @@
 import sys
 import subprocess
 import tempfile
+import uuid
 
 from codecs import open
 
@@ -35,14 +36,19 @@
   args, remaining = parser.parse_known_args()
 
   if args.dependency_out and args.descriptor_set_out:
-    with tempfile.NamedTemporaryFile() as t:
-      custom = [
-          '--descriptor_set_out', args.descriptor_set_out, '--dependency_out',
-          t.name
-      ]
-      subprocess.check_call([args.protoc] + custom + remaining)
-
-      dependency_data = t.read().decode('utf-8')
+    tmp_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
+    custom = [
+        '--descriptor_set_out', args.descriptor_set_out, '--dependency_out',
+        tmp_path
+    ]
+    try:
+      cmd = [args.protoc] + custom + remaining
+      subprocess.check_call(cmd)
+      with open(tmp_path, 'rb') as tmp_rd:
+        dependency_data = tmp_rd.read().decode('utf-8')
+    finally:
+      if os.path.exists(tmp_path):
+        os.unlink(tmp_path)
 
     with open(args.dependency_out, 'w', encoding='utf-8') as f:
       f.write(args.descriptor_set_out + ":")
diff --git a/gn/standalone/sanitizers/BUILD.gn b/gn/standalone/sanitizers/BUILD.gn
index c015d3e..d3374de 100644
--- a/gn/standalone/sanitizers/BUILD.gn
+++ b/gn/standalone/sanitizers/BUILD.gn
@@ -36,11 +36,7 @@
   cflags = []
   defines = []
   if (using_sanitizer) {
-    blacklist_path_ = rebase_path("blacklist.txt", root_build_dir)
-    cflags += [
-      "-fno-omit-frame-pointer",
-      "-fsanitize-blacklist=$blacklist_path_",
-    ]
+    cflags += [ "-fno-omit-frame-pointer" ]
   }
 
   if (is_asan) {
diff --git a/gn/standalone/sanitizers/blacklist.txt b/gn/standalone/sanitizers/blacklist.txt
deleted file mode 100644
index e0e315b..0000000
--- a/gn/standalone/sanitizers/blacklist.txt
+++ /dev/null
@@ -1 +0,0 @@
-# The rules in this file are only applied at compile time.
diff --git a/gn/standalone/sanitizers/sanitizers.gni b/gn/standalone/sanitizers/sanitizers.gni
index 9b0ec00..7116b7b 100644
--- a/gn/standalone/sanitizers/sanitizers.gni
+++ b/gn/standalone/sanitizers/sanitizers.gni
@@ -16,20 +16,18 @@
 import("//gn/standalone/sanitizers/vars.gni")
 import("//gn/standalone/toolchain/llvm.gni")
 
-declare_args() {
-  sanitizer_lib_base_name_ = ""
-  if (is_asan || is_tsan || is_ubsan) {
-    if (is_asan) {
-      sanitizer_lib_base_name_ = "clang_rt.asan"
-    }
-    if (is_tsan) {
-      sanitizer_lib_base_name_ = "clang_rt.tsan"
-    }
-    if (is_ubsan) {
-      sanitizer_lib_base_name_ = "clang_rt.ubsan"
-      if (is_android || is_linux) {
-        sanitizer_lib_base_name_ += "_standalone"
-      }
+_sanitizer_lib_base_name = ""
+if (is_asan || is_tsan || is_ubsan) {
+  if (is_asan) {
+    _sanitizer_lib_base_name = "clang_rt.asan"
+  }
+  if (is_tsan) {
+    _sanitizer_lib_base_name = "clang_rt.tsan"
+  }
+  if (is_ubsan) {
+    _sanitizer_lib_base_name = "clang_rt.ubsan"
+    if (is_android || is_linux) {
+      _sanitizer_lib_base_name += "_standalone"
     }
   }
 }
@@ -38,18 +36,20 @@
   sanitizer_lib_dir = ""
   sanitizer_lib = ""
   sanitizer_lib_dir_is_static = false
-  if (sanitizer_lib_base_name_ != "") {
+  if (_sanitizer_lib_base_name != "") {
     if (is_mac) {
-      sanitizer_lib = "${sanitizer_lib_base_name_}_osx_dynamic"
+      sanitizer_lib = "${_sanitizer_lib_base_name}_osx_dynamic"
       sanitizer_lib_dir = mac_clangrt_dir
     }
     if (is_linux) {
-      sanitizer_lib = "lib${sanitizer_lib_base_name_}-x86_64.a"
+      sanitizer_lib = "lib${_sanitizer_lib_base_name}-x86_64.a"
       sanitizer_lib_dir_is_static = true
-      sanitizer_lib_dir = linux_clangrt_dir
+
+      # sanitizer_lib_dir is unused on linux. All usages of sanitizer_lib_dir
+      # are gated by an if (!sanitizer_lib_dir_is_static).
     }
     if (is_android) {
-      sanitizer_lib = "${sanitizer_lib_base_name_}-${android_llvm_arch}-android"
+      sanitizer_lib = "${_sanitizer_lib_base_name}-${android_llvm_arch}-android"
       sanitizer_lib_dir = android_clangrt_dir
     }
   }
diff --git a/gn/standalone/toolchain/BUILD.gn b/gn/standalone/toolchain/BUILD.gn
index 9f9bd61..c7d8d48 100644
--- a/gn/standalone/toolchain/BUILD.gn
+++ b/gn/standalone/toolchain/BUILD.gn
@@ -16,6 +16,7 @@
 import("//gn/standalone/android.gni")
 import("//gn/standalone/wasm.gni")
 import("llvm.gni")
+import("msvc.gni")
 
 # This file is evaluated once, within the context of the default toolchain,
 # which is the target toolchain.
@@ -46,31 +47,36 @@
 # First of all determine the host toolchain. The user can override this by:
 # 1. setting ar/cc/cxx vars in args.gn.
 # 2. setting is_system_compiler=true in args.gn and the env vars AR/CC/CXX.
-#    This is used by OSSFuzz.
+#    This is used by OSSFuzz and CrOS ebuilds.
+
 declare_args() {
   sysroot = ""
   gcc_toolchain = ""
   ar = "ar"
+  linker = ""
+
   if (is_linux_host) {
     linker = "gold"
-  } else {
-    linker = ""
   }
 
-  if (is_system_compiler) {
-    ar = "\$AR"
-    cc = "\$CC"
-    cxx = "\$CXX"
-  } else if (is_clang) {
-    if (is_linux_host) {
+  if (is_clang) {
+    if (is_linux_host && !is_system_compiler) {
       cc = linux_clang_bin
       cxx = linux_clangxx_bin
       linker = linux_clang_linker
+    } else if (is_win_host && !is_system_compiler) {
+      cc = win_clang_bin
+      cxx = win_clangxx_bin
+      linker = win_clang_linker
     } else {
       cc = "clang"
       cxx = "clang++"
       linker = ""
     }
+  } else if (is_win) {  # MSVC
+    cc = "${win_msvc_bin_dir}\\cl.exe"
+    cxx = "${win_msvc_bin_dir}\\cl.exe"
+    linker = "${win_msvc_bin_dir}\\link.exe"
   } else {  # GCC
     cc = "gcc"
     cxx = "g++"
@@ -123,18 +129,19 @@
 }
 
 declare_args() {
-  if (!is_cross_compiling || is_perfetto_build_generator) {
+  if (is_linux || is_android) {
+    target_linker = "gold"
+  } else {
+    target_linker = ""
+  }
+
+  if (!is_cross_compiling || is_perfetto_build_generator ||
+      is_system_compiler) {
     target_ar = ar
     target_cc = cc
     target_cxx = cxx
-    target_linker = linker
   } else {
     target_ar = "ar"
-    if (is_linux || is_android) {
-      target_linker = "gold"
-    } else {
-      target_linker = ""
-    }
     if (is_android) {
       target_ar = "$android_toolchain_root/bin/$android_abi_target-ar"
       target_cc = "$android_llvm_dir/bin/clang"
@@ -337,3 +344,112 @@
   cc = "$emsdk_dir/emscripten/emcc --em-config $em_config"
   cxx = "$emsdk_dir/emscripten/em++ --em-config $em_config"
 }
+
+# This is used both for MSVC anc clang-cl. clang-cl cmdline interface pretends
+# to be MSVC's cl.exe.
+toolchain("msvc") {
+  lib_switch = ""
+  lib_dir_switch = "/LIBPATH:"
+
+  sys_lib_flags = "/LIBPATH:\"${win_sdk_lib_dir}\\ucrt\\x64\" "
+  sys_lib_flags += "/LIBPATH:\"${win_sdk_lib_dir}\\um\\x64\" "
+  sys_lib_flags += "/LIBPATH:\"${win_msvc_lib_dir}\" "
+
+  # Note: /showIncludes below is required for ninja, to build a complete
+  # dependency graph for headers. Removing it breaks incremental builds.
+
+  tool("cc") {
+    precompiled_header_type = "msvc"
+    pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
+    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    depsformat = "msvc"
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
+    description = "compile {{source}}"
+  }
+
+  tool("cxx") {
+    precompiled_header_type = "msvc"
+    pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
+    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    depsformat = "msvc"
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
+    description = "compile {{source}}"
+  }
+
+  tool("alink") {
+    rspfile = "{{output}}.rsp"
+    command = "$linker /lib /nologo /ignore:4221 {{arflags}} /OUT:{{output}} @$rspfile"
+    outputs = [
+      # Ignore {{output_extension}} and always use .lib, there's no reason to
+      # allow targets to override this extension on Windows.
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
+    ]
+    default_output_extension = ".lib"
+    default_output_dir = "{{target_out_dir}}"
+
+    # inputs_newline works around a fixed per-line buffer size in the linker.
+    rspfile_content = "{{inputs_newline}}"
+    description = "link {{output}}"
+  }
+
+  tool("solink") {
+    dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+    libname = "${dllname}.lib"
+    pdbname = "${dllname}.pdb"
+    rspfile = "${dllname}.rsp"
+
+    command = "$linker /nologo /IMPLIB:$libname ${sys_lib_flags} /DLL /OUT:$dllname /PDB:$pdbname @$rspfile"
+    outputs = [
+      dllname,
+      libname,
+      pdbname,
+    ]
+    default_output_extension = ".dll"
+    default_output_dir = "{{root_out_dir}}"
+
+    link_output = libname
+    depend_output = libname
+    runtime_outputs = [
+      dllname,
+      pdbname,
+    ]
+
+    # Since the above commands only updates the .lib file when it changes, ask
+    # Ninja to check if the timestamp actually changed to know if downstream
+    # dependencies should be recompiled.
+    restat = true
+
+    # inputs_newline works around a fixed per-line buffer size in the linker.
+    rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
+    description = "link {{output}}"
+  }
+
+  tool("link") {
+    exename = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
+    pdbname = "$exename.pdb"
+    rspfile = "$exename.rsp"
+
+    command =
+        "$linker /nologo /OUT:$exename ${sys_lib_flags} /PDB:$pdbname @$rspfile"
+    default_output_extension = ".exe"
+    default_output_dir = "{{root_out_dir}}"
+    outputs = [ exename ]
+
+    # inputs_newline works around a fixed per-line buffer size in the linker.
+    rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
+    description = "link {{output}}"
+  }
+
+  tool("stamp") {
+    command = "cmd /c type nul > \"{{output}}\""
+    description = "stamp {{output}}"
+  }
+
+  tool("copy") {
+    cp_py = rebase_path("../cp.py")
+    command = "cmd.exe /c python \"$cp_py\" {{source}} {{output}}"
+    description = "copy {{source}} {{output}}"
+  }
+}
diff --git a/gn/standalone/toolchain/llvm.gni b/gn/standalone/toolchain/llvm.gni
index dda60ab..e3fc350 100644
--- a/gn/standalone/toolchain/llvm.gni
+++ b/gn/standalone/toolchain/llvm.gni
@@ -15,31 +15,47 @@
 import("//gn/standalone/sanitizers/vars.gni")
 
 declare_args() {
-  is_hermetic_clang = is_clang && is_linux_host
+  is_hermetic_clang = is_clang && (is_linux_host || is_win_host)
 }
 
 assert(!is_hermetic_clang || is_clang, "is_hermetic_clang requires is_clang")
 
-declare_args() {
-  if (is_linux_host) {
-    if (is_hermetic_clang) {
-      _hermetic_llvm_dir = rebase_path("//buildtools/clang", root_build_dir)
-      linux_clang_bin = "$_hermetic_llvm_dir/bin/clang"
-      linux_clangxx_bin = "$_hermetic_llvm_dir/bin/clang++"
-      linux_clangrt_dir = "$_hermetic_llvm_dir/lib/clang/9.0.0/lib/linux"
-      linux_clang_linker = "lld"
-    } else if (is_clang) {
-      # Guess the path for the system clang.
-      find_llvm_out = exec_script("linux_find_llvm.py", [], "list lines")
-      linux_llvm_dir = find_llvm_out[0]
-      linux_clang_bin = find_llvm_out[1]
-      linux_clangxx_bin = find_llvm_out[2]
-      linux_clangrt_dir = "$linux_llvm_dir/lib/linux"
-      linux_clang_linker = "gold"
-    }
-  } else if (is_mac) {
-    mac_toolchain_dirs_ = exec_script("mac_find_llvm.py", [], "list lines")
-    mac_toolchain_dir = mac_toolchain_dirs_[0]
-    mac_clangrt_dir = mac_toolchain_dirs_[1]
+if (is_linux_host) {
+  if (is_hermetic_clang) {
+    _hermetic_llvm_dir =
+        rebase_path("//buildtools/linux64/clang", root_build_dir)
+    linux_clang_bin = "$_hermetic_llvm_dir/bin/clang"
+    linux_clangxx_bin = "$_hermetic_llvm_dir/bin/clang++"
+    linux_clang_linker = "lld"
+  } else if (is_clang && !is_system_compiler) {
+    # Guess the path for the system clang.
+    # When is_system_compiler = true users neet to explicitly set cc / target_cc
+    # vars in the GN args."
+    _find_llvm_out = exec_script("linux_find_llvm.py", [], "list lines")
+    _linux_llvm_dir = _find_llvm_out[0]
+    linux_clang_bin = _find_llvm_out[1]
+    linux_clangxx_bin = _find_llvm_out[2]
+    linux_clang_linker = "gold"
+  }
+} else if (is_mac_host && is_clang && !is_system_compiler) {
+  _mac_toolchain_dirs = exec_script("mac_find_llvm.py", [], "list lines")
+
+  # _mac_toolchain_dirs[0] contains the mac toolchain dir.
+  mac_clangrt_dir = _mac_toolchain_dirs[1]
+} else if (is_win_host) {
+  if (is_hermetic_clang) {
+    # Use the toolchain pulled by //tools/install-build-deps. This tracks
+    # chromium's llvm dist.
+    _llvm_win_path = rebase_path("//buildtools/win/clang/bin", root_build_dir)
+    win_clang_bin = "${_llvm_win_path}\clang-cl.exe"
+    win_clangxx_bin = "${_llvm_win_path}\clang-cl.exe"
+    win_clang_linker = "${_llvm_win_path}\lld-link.exe"
+  } else {
+    # Assume clang-cl.exe / lld-link.exe are on the PATH. The user can always
+    # ovveride them by setting cc/cxx/linker GN variables.
+    # See //gn/standalone/toolchain/BUILD.gn.
+    win_clang_bin = "clang-cl.exe"
+    win_clangxx_bin = "clang-cl.exe"
+    win_clang_linker = "lld-link.exe"
   }
 }
diff --git a/gn/standalone/toolchain/msvc.gni b/gn/standalone/toolchain/msvc.gni
new file mode 100644
index 0000000..34c0a50
--- /dev/null
+++ b/gn/standalone/toolchain/msvc.gni
@@ -0,0 +1,57 @@
+# 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.
+
+# We should never get in here by accident from the Chromium tree.
+assert(!defined(build_with_chromium) || !build_with_chromium)
+
+if (is_win_host) {
+  _find_msvc_out = exec_script("win_find_msvc.py", [], "list lines")
+
+  # The output looks like this (without the line number "N:" part):
+  # 1: C:\Program Files (x86)\Windows Kits\10
+  # 2: 10.0.19041.0
+  # 3: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.28.29333
+  _win_sdk_base = _find_msvc_out[0]
+  _win_sdk_ver = _find_msvc_out[1]
+  _win_msvc_base = _find_msvc_out[2]
+
+  # TODO(primiano): look into how to integrate this with the toolchain pulled by
+  # depot tools. Also improve error reporting telling the user what to do.
+  # For now this requires both:
+  # 1. Build Tools for Visual Studio 2019
+  #    https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019
+  # 2. Windows 10 SDK:
+  #    https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/
+
+  # These variables are required both for clang-cl.exe and MSVC (cl.exe).
+  win_sdk_lib_dir = _win_sdk_base + "\\Lib\\" + _win_sdk_ver
+  win_msvc_lib_dir = _win_msvc_base + "\\lib\\x64"
+
+  # These variables are only required when building with MSVC.
+  # Clang is clever enough to figure out the right include path by querying the
+  # registry and detect the Windows SDK path (it still needs the /LIBPATH
+  # though, hence the _lib_dir above).
+  win_msvc_bin_dir = _win_msvc_base + "\\bin\\Hostx64\\x64"
+  win_msvc_inc_dirs = [
+    _win_msvc_base + "\\include",
+    _win_sdk_base + "\\Include\\" + _win_sdk_ver + "\\ucrt",
+    _win_sdk_base + "\\Include\\" + _win_sdk_ver + "\\um",
+    _win_sdk_base + "\\Include\\" + _win_sdk_ver + "\\shared",
+  ]
+} else {
+  win_sdk_lib_dir = ""
+  win_msvc_lib_dir = ""
+  win_msvc_bin_dir = ""
+  win_msvc_inc_dirs = []
+}
diff --git a/gn/standalone/toolchain/win_find_msvc.py b/gn/standalone/toolchain/win_find_msvc.py
new file mode 100644
index 0000000..38ba378
--- /dev/null
+++ b/gn/standalone/toolchain/win_find_msvc.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# Copyright (C) 2017 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.
+"""
+Finds and prints MSVC and Windows SDK paths.
+
+It outpus:
+Line 1: the base path of the Windows SDK.
+Line 2: the most recent version of the Windows SDK.
+Line 3: the path of the most recent MSVC.
+
+Example:
+C:\Program Files (x86)\Windows Kits\10
+10.0.19041.0
+C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.28.29333
+"""
+
+import os
+import subprocess
+import sys
+
+
+def ver_to_tuple(ver_str):
+  """Turns '10.1.2' into [10,1,2] so it can be compared using > """
+  parts = [int(x) for x in ver_str.split('.')]
+  assert (len(parts) == 4)
+  return parts
+
+
+def find_max_subdir(base_dir, filter=lambda x: True):
+  """Finds the max subdirectory in base_dir by comparing semantic versions."""
+  max_ver = None
+  for ver in os.listdir(base_dir) if os.path.exists(base_dir) else []:
+    cur = os.path.join(base_dir, ver)
+    if not filter(cur):
+      continue
+    if max_ver is None or ver_to_tuple(ver) > ver_to_tuple(max_ver):
+      max_ver = ver
+  return max_ver
+
+
+def main():
+  out = [
+      '',
+      '',
+      '',
+  ]
+  winsdk_base = 'C:\\Program Files (x86)\\Windows Kits\\10'
+  if os.path.exists(winsdk_base):
+    out[0] = winsdk_base
+    lib_base = winsdk_base + '\\Lib'
+    filt = lambda x: os.path.exists(os.path.join(x, 'ucrt', 'x64', 'ucrt.lib'))
+    out[1] = find_max_subdir(lib_base, filt)
+
+  msvc_base = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC'
+  if os.path.exists(msvc_base):
+    filt = lambda x: os.path.exists(os.path.join(x, 'lib', 'x64', 'libcmt.lib'))
+    max_msvc = find_max_subdir(msvc_base, filt)
+    if max_msvc is not None:
+      out[2] = os.path.join(msvc_base, max_msvc)
+
+  # Don't error in case of failure, GN scripts are supposed to deal with
+  # failures and allow the user to ovveride the dirs.
+
+  print('\n'.join(out))
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/gn/standalone/wasm_typescript_declaration.d.ts b/gn/standalone/wasm_typescript_declaration.d.ts
index 9fa3538..67b0c9b 100644
--- a/gn/standalone/wasm_typescript_declaration.d.ts
+++ b/gn/standalone/wasm_typescript_declaration.d.ts
@@ -63,6 +63,6 @@
     print(s: string): void;
     printErr(s: string): void;
     onRuntimeInitialized(): void;
-    onAbort(): void;
+    onAbort?(): void;
   }
 }
diff --git a/gn/standalone/write_ui_dist_file_map.py b/gn/standalone/write_ui_dist_file_map.py
index 999bea5..20ba344 100644
--- a/gn/standalone/write_ui_dist_file_map.py
+++ b/gn/standalone/write_ui_dist_file_map.py
@@ -72,7 +72,8 @@
     fname = fname[len(strip):]
     # We use b64 instead of hexdigest() because it's handy for handling fetch()
     # subresource integrity.
-    contents += '    \'%s\': \'sha256-%s\',\n' % (fname, b64encode(digest))
+    contents += '    \'%s\': \'sha256-%s\',\n' % (
+        fname, b64encode(digest).decode("ascii"))
   contents += '  },\n'
 
   # Compute the hash of the all resources' hashes.
diff --git a/include/perfetto/base/BUILD.gn b/include/perfetto/base/BUILD.gn
index 85819b7..66b9908 100644
--- a/include/perfetto/base/BUILD.gn
+++ b/include/perfetto/base/BUILD.gn
@@ -21,7 +21,9 @@
     "export.h",
     "flat_set.h",
     "logging.h",
+    "platform_handle.h",
     "proc_utils.h",
+    "status.h",
     "task_runner.h",
     "thread_utils.h",
     "time.h",
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index 747965e..bd3623f 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -26,16 +26,17 @@
 
 #if defined(__ANDROID__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 1
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #elif defined(__APPLE__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 1
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 1
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
@@ -44,55 +45,62 @@
 // Include TARGET_OS_IPHONE when on __APPLE__ systems.
 #include <TargetConditionals.h>
 #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 1
 #else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 1
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #endif
 #elif defined(__linux__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 1
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #elif defined(_WIN32)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 1
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #elif defined(__EMSCRIPTEN__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 1
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #elif defined(__Fuchsia__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 1
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #elif defined(__native_client__)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() 0
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_APPLE() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() 0
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WASM() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_FUCHSIA() 0
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_NACL() 1
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_IOS() 0
 #else
 #error OS not supported (see build_config.h)
 #endif
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index 559250f..7a34606 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -31,12 +31,12 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_ON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_OFF() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERBOSE_LOGS() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERSION_GEN() (0)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERSION_GEN() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_PERCENTILE() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (0)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX())
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
 
 // clang-format on
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index 8201443..ae45e29 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -31,12 +31,12 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_ON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_OFF() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERBOSE_LOGS() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERSION_GEN() (0)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERSION_GEN() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_PERCENTILE() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX())
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC())
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MACOSX())
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
 
 // clang-format on
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index 334c3f9..bb8b337 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -17,12 +17,18 @@
 #ifndef INCLUDE_PERFETTO_BASE_COMPILER_H_
 #define INCLUDE_PERFETTO_BASE_COMPILER_H_
 
+#include <stddef.h>
 #include <type_traits>
 
 #include "perfetto/base/build_config.h"
 
+#if defined(__GNUC__) || defined(__clang__)
 #define PERFETTO_LIKELY(_x) __builtin_expect(!!(_x), 1)
 #define PERFETTO_UNLIKELY(_x) __builtin_expect(!!(_x), 0)
+#else
+#define PERFETTO_LIKELY(_x) (_x)
+#define PERFETTO_UNLIKELY(_x) (_x)
+#endif
 
 #if defined(__GNUC__) || defined(__clang__)
 #define PERFETTO_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
@@ -66,6 +72,28 @@
 #define PERFETTO_THREAD_LOCAL thread_local
 #endif
 
+#if defined(__GNUC__) || defined(__clang__)
+#define PERFETTO_POPCOUNT(x) __builtin_popcountll(x)
+#else
+#include <intrin.h>
+#define PERFETTO_POPCOUNT(x) __popcnt64(x)
+#endif
+
+#if defined(__clang__)
+#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
+extern "C" void __asan_poison_memory_region(void const volatile*, size_t);
+extern "C" void __asan_unpoison_memory_region(void const volatile*, size_t);
+#define PERFETTO_ASAN_POISON(a, s) __asan_poison_memory_region((a), (s))
+#define PERFETTO_ASAN_UNPOISON(a, s) __asan_unpoison_memory_region((a), (s))
+#else
+#define PERFETTO_ASAN_POISON(addr, size)
+#define PERFETTO_ASAN_UNPOISON(addr, size)
+#endif  // __has_feature(address_sanitizer)
+#else
+#define PERFETTO_ASAN_POISON(addr, size)
+#define PERFETTO_ASAN_UNPOISON(addr, size)
+#endif  // __clang__
+
 namespace perfetto {
 namespace base {
 
diff --git a/include/perfetto/base/flat_set.h b/include/perfetto/base/flat_set.h
index 068ad3c..9390537 100644
--- a/include/perfetto/base/flat_set.h
+++ b/include/perfetto/base/flat_set.h
@@ -48,7 +48,7 @@
 
   FlatSet() = default;
 
-  // Mainly for tests. Deliberately not marked as "expicit".
+  // Mainly for tests. Deliberately not marked as "explicit".
   FlatSet(std::initializer_list<T> initial) : entries_(initial) {
     std::sort(entries_.begin(), entries_.end());
     entries_.erase(std::unique(entries_.begin(), entries_.end()),
diff --git a/include/perfetto/base/logging.h b/include/perfetto/base/logging.h
index b90a931..3cef830 100644
--- a/include/perfetto/base/logging.h
+++ b/include/perfetto/base/logging.h
@@ -24,8 +24,10 @@
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/export.h"
 
+#if defined(__GNUC__) || defined(__clang__)
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
 #pragma GCC system_header
+#endif
 
 // TODO(primiano): move this to base/build_config.h, turn into
 // PERFETTO_BUILDFLAG(DCHECK_IS_ON) and update call sites to use that instead.
@@ -99,11 +101,19 @@
                                __LINE__, fmt, ##__VA_ARGS__)
 #endif
 
+#if defined(_MSC_VER)
+#define PERFETTO_IMMEDIATE_CRASH() \
+  do {                             \
+    __debugbreak();                \
+    __assume(0);                   \
+  } while (0)
+#else
 #define PERFETTO_IMMEDIATE_CRASH() \
   do {                             \
     __builtin_trap();              \
     __builtin_unreachable();       \
   } while (0)
+#endif
 
 #if PERFETTO_BUILDFLAG(PERFETTO_VERBOSE_LOGS)
 #define PERFETTO_LOG(fmt, ...) \
@@ -122,8 +132,13 @@
     PERFETTO_IMMEDIATE_CRASH();        \
   } while (0)
 
+#if defined(__GNUC__) || defined(__clang__)
 #define PERFETTO_PLOG(x, ...) \
   PERFETTO_ELOG(x " (errno: %d, %s)", ##__VA_ARGS__, errno, strerror(errno))
+#else
+// MSVC expands __VA_ARGS__ in a different order. Give up, not worth it.
+#define PERFETTO_PLOG PERFETTO_ELOG
+#endif
 
 #define PERFETTO_CHECK(x)                            \
   do {                                               \
@@ -138,8 +153,13 @@
 #define PERFETTO_DLOG(fmt, ...) \
   PERFETTO_XLOG(::perfetto::base::kLogDebug, fmt, ##__VA_ARGS__)
 
+#if defined(__GNUC__) || defined(__clang__)
 #define PERFETTO_DPLOG(x, ...) \
   PERFETTO_DLOG(x " (errno: %d, %s)", ##__VA_ARGS__, errno, strerror(errno))
+#else
+// MSVC expands __VA_ARGS__ in a different order. Give up, not worth it.
+#define PERFETTO_DPLOG PERFETTO_DLOG
+#endif
 
 #else  // PERFETTO_DLOG_IS_ON()
 
diff --git a/include/perfetto/base/platform_handle.h b/include/perfetto/base/platform_handle.h
new file mode 100644
index 0000000..879fa85
--- /dev/null
+++ b/include/perfetto/base/platform_handle.h
@@ -0,0 +1,61 @@
+/*
+ * 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 INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
+#define INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
+
+#include "perfetto/base/build_config.h"
+
+namespace perfetto {
+namespace base {
+
+// PlatformHandle should be used only for types that are HANDLE(s) in Windows.
+// It should NOT be used to blanket-replace "int fd" in the codebase.
+// Windows has two types of "handles", which, in UNIX-land, both map to int:
+// 1. File handles returned by the posix-compatibility API like _open().
+//    These are just int(s) and should stay such, because all the posix-like API
+//    in Windows.h take an int, not a HANDLE.
+// 2. Handles returned by old-school WINAPI like CreateFile, CreateEvent etc.
+//    These are proper HANDLE(s). PlatformHandle should be used here.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+// Windows.h typedefs HANDLE to void*. We use void* here to avoid leaking
+// Windows.h through our headers.
+using PlatformHandle = void*;
+
+// On Windows both nullptr and 0xffff... (INVALID_HANDLE_VALUE) are invalid.
+struct PlatformHandleChecker {
+  static inline bool IsValid(PlatformHandle h) {
+    return h && h != reinterpret_cast<PlatformHandle>(-1);
+  }
+};
+#else
+using PlatformHandle = int;
+struct PlatformHandleChecker {
+  static inline bool IsValid(PlatformHandle h) { return h >= 0; }
+};
+#endif
+
+// The definition of this lives in base/file_utils.cc (to avoid creating an
+// extra build edge for a one liner). This is really an alias for close() (UNIX)
+// CloseHandle() (Windows). THe indirection layer is just to avoid leaking
+// system headers like Windows.h through perfetto headers.
+// Thre return value is always UNIX-style: 0 on success, -1 on failure.
+int ClosePlatformHandle(PlatformHandle);
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_BASE_PLATFORM_HANDLE_H_
diff --git a/include/perfetto/base/status.h b/include/perfetto/base/status.h
new file mode 100644
index 0000000..c48d86a
--- /dev/null
+++ b/include/perfetto/base/status.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_
+#define INCLUDE_PERFETTO_BASE_STATUS_H_
+
+#include <string>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/export.h"
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace base {
+
+// Represents either the success or the failure message of a function.
+// 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).
+class PERFETTO_EXPORT Status {
+ public:
+  Status() : ok_(true) {}
+  explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) {
+    PERFETTO_CHECK(!message_.empty());
+  }
+
+  // Copy operations.
+  Status(const Status&) = default;
+  Status& operator=(const Status&) = default;
+
+  // Move operations. The moved-from state is valid but unspecified.
+  Status(Status&&) noexcept = default;
+  Status& operator=(Status&&) = default;
+
+  bool ok() const { return ok_; }
+
+  // When ok() is false this returns the error message. Returns the empty string
+  // otherwise.
+  const std::string& message() const { return message_; }
+  const char* c_message() const { return message_.c_str(); }
+
+ private:
+  bool ok_ = false;
+  std::string message_;
+};
+
+// Returns a status object which represents the Ok status.
+inline Status OkStatus() {
+  return Status();
+}
+
+PERFETTO_PRINTF_FORMAT(1, 2) Status ErrStatus(const char* format, ...);
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_BASE_STATUS_H_
diff --git a/include/perfetto/base/task_runner.h b/include/perfetto/base/task_runner.h
index 040aab2..4f64bf8 100644
--- a/include/perfetto/base/task_runner.h
+++ b/include/perfetto/base/task_runner.h
@@ -22,6 +22,7 @@
 #include <functional>
 
 #include "perfetto/base/export.h"
+#include "perfetto/base/platform_handle.h"
 
 namespace perfetto {
 namespace base {
@@ -49,18 +50,19 @@
   // called from any thread.
   virtual void PostDelayedTask(std::function<void()>, uint32_t delay_ms) = 0;
 
-  // Schedule a task to run when |fd| becomes readable. The same |fd| can only
-  // be monitored by one function. Note that this function only needs to be
-  // implemented on platforms where the built-in ipc framework is used. Can be
-  // called from any thread.
+  // Schedule a task to run when the handle becomes readable. The same handle
+  // can only be monitored by one function. Note that this function only needs
+  // to be implemented on platforms where the built-in ipc framework is used.
+  // Can be called from any thread.
   // TODO(skyostil): Refactor this out of the shared interface.
-  virtual void AddFileDescriptorWatch(int fd, std::function<void()>) = 0;
+  virtual void AddFileDescriptorWatch(PlatformHandle,
+                                      std::function<void()>) = 0;
 
-  // Remove a previously scheduled watch for |fd|. If this is run on the target
-  // thread of this TaskRunner, guarantees that the task registered to this fd
-  // will not be executed after this function call. Can be called from any
-  // thread.
-  virtual void RemoveFileDescriptorWatch(int fd) = 0;
+  // Remove a previously scheduled watch for the handle. If this is run on the
+  // target thread of this TaskRunner, guarantees that the task registered to
+  // this handle will not be executed after this function call.
+  // Can be called from any thread.
+  virtual void RemoveFileDescriptorWatch(PlatformHandle) = 0;
 
   // Checks if the current thread is the same thread where the TaskRunner's task
   // run. This allows single threaded task runners (like the ones used in
diff --git a/include/perfetto/base/thread_utils.h b/include/perfetto/base/thread_utils.h
index 39f2f1b..48a6508 100644
--- a/include/perfetto/base/thread_utils.h
+++ b/include/perfetto/base/thread_utils.h
@@ -54,7 +54,7 @@
 inline PlatformThreadId GetThreadId() {
   return zx_thread_self();
 }
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 using PlatformThreadId = uint64_t;
 inline PlatformThreadId GetThreadId() {
   uint64_t tid;
diff --git a/include/perfetto/base/time.h b/include/perfetto/base/time.h
index 0ee6042..de966f5 100644
--- a/include/perfetto/base/time.h
+++ b/include/perfetto/base/time.h
@@ -20,11 +20,12 @@
 #include <time.h>
 
 #include <chrono>
+#include <string>
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #include <mach/mach_init.h>
 #include <mach/mach_port.h>
 #include <mach/mach_time.h>
@@ -58,7 +59,7 @@
   return GetWallTimeNs();
 }
 
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 
 inline TimeNanos GetWallTimeNs() {
   auto init_time_factor = []() -> uint64_t {
@@ -179,6 +180,8 @@
   return ts;
 }
 
+std::string GetTimeFmt(const std::string& fmt);
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index e2d29b5..694d23d 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -18,10 +18,10 @@
   sources = [
     "circular_queue.h",
     "container_annotations.h",
+    "endian.h",
     "event_fd.h",
     "file_utils.h",
     "hash.h",
-    "lookup_set.h",
     "metatrace.h",
     "metatrace_events.h",
     "no_destructor.h",
@@ -43,6 +43,7 @@
     "unix_task_runner.h",
     "utils.h",
     "uuid.h",
+    "version.h",
     "waitable_event.h",
     "watchdog.h",
     "watchdog_noop.h",
diff --git a/include/perfetto/ext/base/circular_queue.h b/include/perfetto/ext/base/circular_queue.h
index 18ca770..8d1f1b7 100644
--- a/include/perfetto/ext/base/circular_queue.h
+++ b/include/perfetto/ext/base/circular_queue.h
@@ -67,26 +67,22 @@
       ignore_result(generation);
     }
 
-    T* operator->() {
+    Iterator(const Iterator&) noexcept = default;
+    Iterator& operator=(const Iterator&) noexcept = default;
+    Iterator(Iterator&&) noexcept = default;
+    Iterator& operator=(Iterator&&) noexcept = default;
+
+    T* operator->() const {
 #if PERFETTO_DCHECK_IS_ON()
       PERFETTO_DCHECK(generation_ == queue_->generation());
 #endif
       return queue_->Get(pos_);
     }
 
-    const T* operator->() const {
-      return const_cast<CircularQueue<T>::Iterator*>(this)->operator->();
-    }
-
-    T& operator*() { return *(operator->()); }
-    const T& operator*() const { return *(operator->()); }
+    T& operator*() const { return *(operator->()); }
 
     value_type& operator[](difference_type i) { return *(*this + i); }
 
-    const value_type& operator[](difference_type i) const {
-      return const_cast<CircularQueue<T>::Iterator&>(*this)[i];
-    }
-
     Iterator& operator++() {
       Add(1);
       return *this;
diff --git a/include/perfetto/ext/base/endian.h b/include/perfetto/ext/base/endian.h
new file mode 100644
index 0000000..3d09833
--- /dev/null
+++ b/include/perfetto/ext/base/endian.h
@@ -0,0 +1,57 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_BASE_ENDIAN_H_
+#define INCLUDE_PERFETTO_EXT_BASE_ENDIAN_H_
+
+#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
+#error "endian.h supports only little-endian archs"
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>  // For MSVC
+
+#include "perfetto/base/build_config.h"
+
+namespace perfetto {
+namespace base {
+
+#if PERFETTO_BUILDFLAG(PERFETTO_COMPILER_MSVC)
+inline uint16_t HostToBE16(uint16_t x) {
+  return _byteswap_ushort(x);
+}
+inline uint32_t HostToBE32(uint32_t x) {
+  return _byteswap_ulong(x);
+}
+inline uint64_t HostToBE64(uint64_t x) {
+  return _byteswap_uint64(x);
+}
+#else
+inline uint16_t HostToBE16(uint16_t x) {
+  return __builtin_bswap16(x);
+}
+inline uint32_t HostToBE32(uint32_t x) {
+  return __builtin_bswap32(x);
+}
+inline uint64_t HostToBE64(uint64_t x) {
+  return __builtin_bswap64(x);
+}
+#endif
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_ENDIAN_H_
diff --git a/include/perfetto/ext/base/event_fd.h b/include/perfetto/ext/base/event_fd.h
index 9e1715b..e4bd6e9 100644
--- a/include/perfetto/ext/base/event_fd.h
+++ b/include/perfetto/ext/base/event_fd.h
@@ -18,15 +18,9 @@
 #define INCLUDE_PERFETTO_EXT_BASE_EVENT_FD_H_
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/platform_handle.h"
 #include "perfetto/ext/base/scoped_file.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-#define PERFETTO_USE_EVENTFD() 1
-#else
-#define PERFETTO_USE_EVENTFD() 0
-#endif
-
 namespace perfetto {
 namespace base {
 
@@ -41,7 +35,7 @@
   EventFd& operator=(EventFd&&) = default;
 
   // The non-blocking file descriptor that can be polled to wait for the event.
-  int fd() const { return fd_.get(); }
+  PlatformHandle fd() const { return event_handle_.get(); }
 
   // Can be called from any thread.
   void Notify();
@@ -53,9 +47,12 @@
  private:
   // The eventfd, when eventfd is supported, otherwise this is the read end of
   // the pipe for fallback mode.
-  ScopedFile fd_;
+  ScopedPlatformHandle event_handle_;
 
-#if !PERFETTO_USE_EVENTFD()
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) &&   \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // On Mac and other non-Linux UNIX platforms a pipe-based fallback is used.
   // The write end of the wakeup pipe.
   ScopedFile write_fd_;
 #endif
diff --git a/include/perfetto/ext/base/file_utils.h b/include/perfetto/ext/base/file_utils.h
index 9ff03b1..bf33d70 100644
--- a/include/perfetto/ext/base/file_utils.h
+++ b/include/perfetto/ext/base/file_utils.h
@@ -17,19 +17,36 @@
 #ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_
 #define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_
 
+#include <fcntl.h>  // For mode_t & O_RDONLY/RDWR. Exists also on Windows.
 #include <stddef.h>
 
 #include <string>
 
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/export.h"
+#include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
 
 namespace perfetto {
 namespace base {
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+using FileOpenMode = int;
+#else
+using FileOpenMode = mode_t;
+#endif
+
+constexpr FileOpenMode kFileModeInvalid = static_cast<FileOpenMode>(-1);
+
+bool ReadPlatformHandle(PlatformHandle, std::string* out);
 bool ReadFileDescriptor(int fd, std::string* out);
 bool ReadFileStream(FILE* f, std::string* out);
 bool ReadFile(const std::string& path, std::string* out);
 
+// A wrapper around read(2). It deals with Linux vs Windows includes. It also
+// deals with handling EINTR. Has the same semantics of UNIX's read(2).
+ssize_t Read(int fd, void* dst, size_t dst_size);
+
 // Call write until all data is written or an error is detected.
 //
 // man 2 write:
@@ -39,8 +56,28 @@
 //   succeeds, and returns the number of bytes written.
 ssize_t WriteAll(int fd, const void* buf, size_t count);
 
+ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count);
+
+ScopedFile OpenFile(const std::string& path,
+                    int flags,
+                    FileOpenMode = kFileModeInvalid);
+
+// This is an alias for close(). It's to avoid leaking Windows.h in headers.
+// Exported because ScopedFile is used in the /include/ext API by Chromium
+// component builds.
+int PERFETTO_EXPORT CloseFile(int fd);
+
 bool FlushFile(int fd);
 
+// Returns true if mkdir succeeds, false if it fails (see errno in that case).
+bool Mkdir(const std::string& path);
+
+// Calls rmdir() on UNIX, _rmdir() on Windows.
+bool Rmdir(const std::string& path);
+
+// Wrapper around access(path, F_OK).
+bool FileExists(const std::string& path);
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/base/lookup_set.h b/include/perfetto/ext/base/lookup_set.h
deleted file mode 100644
index 2e31a52..0000000
--- a/include/perfetto/ext/base/lookup_set.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef INCLUDE_PERFETTO_EXT_BASE_LOOKUP_SET_H_
-#define INCLUDE_PERFETTO_EXT_BASE_LOOKUP_SET_H_
-
-#include <set>
-
-namespace perfetto {
-namespace base {
-
-// Set that allows lookup from const member of the object.
-template <typename T, typename U, U T::*p>
-class LookupSet {
- public:
-  T* Get(const U& key) {
-    // This will be nicer with C++14 transparent comparators.
-    // Then we will be able to look up by just the key using a sutiable
-    // comparator.
-    //
-    // For now we need to allow to construct a T from the key.
-    T node(key);
-    auto it = set_.find(node);
-    if (it == set_.end())
-      return nullptr;
-    return const_cast<T*>(&(*it));
-  }
-
-  template <typename... P>
-  T* Emplace(P&&... args) {
-    auto r = set_.emplace(std::forward<P>(args)...);
-    return const_cast<T*>(&(*r.first));
-  }
-
-  bool Remove(const T& child) { return set_.erase(child); }
-
-  void Clear() { set_.clear(); }
-
-  static_assert(std::is_const<U>::value, "key must be const");
-
- private:
-  class Comparator {
-   public:
-    bool operator()(const T& one, const T& other) const {
-      return (&one)->*p < (&other)->*p;
-    }
-  };
-
-  std::set<T, Comparator> set_;
-};
-
-}  // namespace base
-}  // namespace perfetto
-
-#endif  // INCLUDE_PERFETTO_EXT_BASE_LOOKUP_SET_H_
diff --git a/include/perfetto/ext/base/metatrace.h b/include/perfetto/ext/base/metatrace.h
index f5eb057..97ff14d 100644
--- a/include/perfetto/ext/base/metatrace.h
+++ b/include/perfetto/ext/base/metatrace.h
@@ -26,7 +26,6 @@
 #include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/metatrace_events.h"
-#include "perfetto/ext/base/thread_annotations.h"
 #include "perfetto/ext/base/utils.h"
 
 // A facility to trace execution of the perfetto codebase itself.
@@ -143,7 +142,7 @@
     // Only one of the two elements can be zero initialized, clang complains
     // about "initializing multiple members of union" otherwise.
     uint32_t duration_ns = 0;  // If type == event.
-    int32_t counter_value;  // If type == counter.
+    int32_t counter_value;     // If type == counter.
   };
 };
 
diff --git a/include/perfetto/ext/base/paged_memory.h b/include/perfetto/ext/base/paged_memory.h
index f0ed568..0afd6b8 100644
--- a/include/perfetto/ext/base/paged_memory.h
+++ b/include/perfetto/ext/base/paged_memory.h
@@ -56,9 +56,8 @@
   };
 
   // Allocates |size| bytes using mmap(MAP_ANONYMOUS). The returned memory is
-  // guaranteed to be page-aligned and guaranteed to be zeroed. |size| must be a
-  // multiple of 4KB (a page size). For |flags|, see the AllocationFlags enum
-  // above.
+  // guaranteed to be page-aligned and guaranteed to be zeroed.
+  // For |flags|, see the AllocationFlags enum above.
   static PagedMemory Allocate(size_t size, int flags = 0);
 
   // Hint to the OS that the memory range is not needed and can be discarded.
@@ -88,6 +87,10 @@
   PagedMemory& operator=(const PagedMemory&) = default;
 
   char* p_ = nullptr;
+
+  // The size originally passed to Allocate(). The actual virtual memory
+  // reservation will be larger due to: (i) guard pages; (ii) rounding up to
+  // the system page size.
   size_t size_ = 0;
 
 #if TRACK_COMMITTED_SIZE()
diff --git a/include/perfetto/ext/base/pipe.h b/include/perfetto/ext/base/pipe.h
index ba22729..840296d 100644
--- a/include/perfetto/ext/base/pipe.h
+++ b/include/perfetto/ext/base/pipe.h
@@ -17,6 +17,7 @@
 #ifndef INCLUDE_PERFETTO_EXT_BASE_PIPE_H_
 #define INCLUDE_PERFETTO_EXT_BASE_PIPE_H_
 
+#include "perfetto/base/platform_handle.h"
 #include "perfetto/ext/base/scoped_file.h"
 
 namespace perfetto {
@@ -26,9 +27,11 @@
  public:
   enum Flags {
     kBothBlock = 0,
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
     kBothNonBlock,
     kRdNonBlock,
     kWrNonBlock,
+#endif
   };
 
   static Pipe Create(Flags = kBothBlock);
@@ -37,8 +40,8 @@
   Pipe(Pipe&&) noexcept;
   Pipe& operator=(Pipe&&);
 
-  ScopedFile rd;
-  ScopedFile wr;
+  ScopedPlatformHandle rd;
+  ScopedPlatformHandle wr;
 };
 
 }  // namespace base
diff --git a/include/perfetto/ext/base/scoped_file.h b/include/perfetto/ext/base/scoped_file.h
index 24c8970..978843e 100644
--- a/include/perfetto/ext/base/scoped_file.h
+++ b/include/perfetto/ext/base/scoped_file.h
@@ -19,35 +19,42 @@
 
 #include "perfetto/base/build_config.h"
 
-#include <fcntl.h>
 #include <stdio.h>
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
-    !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
-#include <corecrt_io.h>
-typedef int mode_t;
-#else
-#include <dirent.h>
-#include <unistd.h>
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <dirent.h>  // For DIR* / opendir().
 #endif
 
 #include <string>
 
+#include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/base/platform_handle.h"
 
 namespace perfetto {
 namespace base {
 
-constexpr mode_t kInvalidMode = static_cast<mode_t>(-1);
+namespace internal {
+// Used for the most common cases of ScopedResource where there is only one
+// invalid value.
+template <typename T, T InvalidValue>
+struct DefaultValidityChecker {
+  static bool IsValid(T t) { return t != InvalidValue; }
+};
+}  // namespace internal
 
 // RAII classes for auto-releasing fds and dirs.
+// if T is a pointer type, InvalidValue must be nullptr. Doing otherwise
+// causes weird unexpected behaviors (See https://godbolt.org/z/5nGMW4).
 template <typename T,
           int (*CloseFunction)(T),
           T InvalidValue,
-          bool CheckClose = true>
-class ScopedResource {
+          bool CheckClose = true,
+          class Checker = internal::DefaultValidityChecker<T, InvalidValue>>
+class PERFETTO_EXPORT ScopedResource {
  public:
   explicit ScopedResource(T t = InvalidValue) : t_(t) {}
+  static constexpr T kInvalid = InvalidValue;
   ScopedResource(ScopedResource&& other) noexcept {
     t_ = other.t_;
     other.t_ = InvalidValue;
@@ -59,9 +66,9 @@
   }
   T get() const { return t_; }
   T operator*() const { return t_; }
-  explicit operator bool() const { return t_ != InvalidValue; }
+  explicit operator bool() const { return Checker::IsValid(t_); }
   void reset(T r = InvalidValue) {
-    if (t_ != InvalidValue) {
+    if (Checker::IsValid(t_)) {
       int res = CloseFunction(t_);
       if (CheckClose)
         PERFETTO_CHECK(res == 0);
@@ -78,30 +85,35 @@
  private:
   ScopedResource(const ScopedResource&) = delete;
   ScopedResource& operator=(const ScopedResource&) = delete;
-
   T t_;
 };
 
-using ScopedFile = ScopedResource<int, close, -1>;
-inline static ScopedFile OpenFile(const std::string& path,
-                                  int flags,
-                                  mode_t mode = kInvalidMode) {
-  PERFETTO_DCHECK((flags & O_CREAT) == 0 || mode != kInvalidMode);
+// Declared in file_utils.h. Forward declared to avoid #include cycles.
+int PERFETTO_EXPORT CloseFile(int fd);
+
+// Use this for file resources obtained via open() and similar APIs.
+using ScopedFile = ScopedResource<int, CloseFile, -1>;
+using ScopedFstream = ScopedResource<FILE*, fclose, nullptr>;
+
+// Use this for resources that are HANDLE on Windows. See comments in
+// platform_handle.h
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  // Always use O_BINARY on Windows, to avoid silly EOL translations.
-  ScopedFile fd(open(path.c_str(), flags | O_BINARY, mode));
+using ScopedPlatformHandle = ScopedResource<PlatformHandle,
+                                            ClosePlatformHandle,
+                                            /*InvalidValue=*/nullptr,
+                                            /*CheckClose=*/true,
+                                            PlatformHandleChecker>;
 #else
-  // Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec.
-  ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode));
-#endif
-  return fd;
-}
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+// On non-windows systems we alias ScopedPlatformHandle to ScopedFile because
+// they are really the same. This is to allow assignments between the two in
+// Linux-specific code paths that predate ScopedPlatformHandle.
+static_assert(std::is_same<int, PlatformHandle>::value, "");
+using ScopedPlatformHandle = ScopedFile;
+
+// DIR* does not exist on Windows.
 using ScopedDir = ScopedResource<DIR*, closedir, nullptr>;
 #endif
 
-using ScopedFstream = ScopedResource<FILE*, fclose, nullptr>;
-
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 12fe495..51da19f 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -29,6 +29,8 @@
 namespace perfetto {
 namespace base {
 
+std::string QuoteAndEscapeControlCodes(const std::string& raw);
+
 inline char Lowercase(char c) {
   return ('A' <= c && c <= 'Z') ? static_cast<char>(c - ('A' - 'a')) : c;
 }
@@ -112,9 +114,12 @@
   return ToHex(s.c_str(), s.size());
 }
 std::string IntToHexString(uint32_t number);
+std::string Uint64ToHexString(uint64_t number);
+std::string Uint64ToHexStringNoPrefix(uint64_t number);
 std::string ReplaceAll(std::string str,
                        const std::string& to_replace,
                        const std::string& replacement);
+std::string TrimLeading(const std::string& str);
 
 }  // namespace base
 }  // namespace perfetto
diff --git a/include/perfetto/ext/base/string_writer.h b/include/perfetto/ext/base/string_writer.h
index 428b17e..cee3198 100644
--- a/include/perfetto/ext/base/string_writer.h
+++ b/include/perfetto/ext/base/string_writer.h
@@ -71,7 +71,13 @@
   template <char padchar, uint64_t padding>
   void AppendPaddedInt(int64_t sign_value) {
     const bool negate = std::signbit(static_cast<double>(sign_value));
-    uint64_t absolute_value = static_cast<uint64_t>(std::abs(sign_value));
+    uint64_t absolute_value;
+    if (sign_value == std::numeric_limits<int64_t>::min()) {
+      absolute_value =
+          static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1;
+    } else {
+      absolute_value = static_cast<uint64_t>(std::abs(sign_value));
+    }
     AppendPaddedInt<padchar, padding>(absolute_value, negate);
   }
 
@@ -154,7 +160,9 @@
 
     if (padding > 0) {
       size_t num_digits = kSizeNeeded - 1 - idx;
-      for (size_t i = num_digits; i < padding; i++) {
+      // std::max() needed to work around GCC not being able to tell that
+      // padding > 0.
+      for (size_t i = num_digits; i < std::max(uint64_t{1u}, padding); i++) {
         data[idx--] = padchar;
       }
     }
diff --git a/include/perfetto/ext/base/subprocess.h b/include/perfetto/ext/base/subprocess.h
index 2733152..a57ada9 100644
--- a/include/perfetto/ext/base/subprocess.h
+++ b/include/perfetto/ext/base/subprocess.h
@@ -24,7 +24,7 @@
 // this, the Bazel build breaks on Windows.
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #define PERFETTO_HAS_SUBPROCESS() 1
 #else
 #define PERFETTO_HAS_SUBPROCESS() 0
@@ -116,7 +116,8 @@
   enum OutputMode {
     kInherit = 0,  // Inherit's the caller process stdout/stderr.
     kDevNull,      // dup() onto /dev/null
-    kBuffer        // dup() onto a pipe and move it into the output() buffer.
+    kBuffer,       // dup() onto a pipe and move it into the output() buffer.
+    kFd,           // dup() onto the passed args.fd.
   };
 
   // Input arguments for configuring the subprocess behavior.
@@ -153,10 +154,24 @@
     OutputMode stdout_mode = kInherit;
     OutputMode stderr_mode = kInherit;
 
+    base::ScopedFile out_fd;
+
     // Returns " ".join(exec_cmd), quoting arguments.
     std::string GetCmdString() const;
   };
 
+  struct ResourceUsage {
+    uint32_t cpu_utime_ms = 0;
+    uint32_t cpu_stime_ms = 0;
+    uint32_t max_rss_kb = 0;
+    uint32_t min_page_faults = 0;
+    uint32_t maj_page_faults = 0;
+    uint32_t vol_ctx_switch = 0;
+    uint32_t invol_ctx_switch = 0;
+
+    uint32_t cpu_time_ms() const { return cpu_utime_ms + cpu_stime_ms; }
+  };
+
   explicit Subprocess(std::initializer_list<std::string> exec_cmd = {});
   Subprocess(Subprocess&&) noexcept;
   Subprocess& operator=(Subprocess&&);
@@ -183,16 +198,22 @@
 
   Status Poll();
 
-  // Sends a SIGKILL and wait to see the process termination.
-  void KillAndWaitForTermination();
+  // Sends a signal (SIGKILL if not specified) and wait for process termination.
+  void KillAndWaitForTermination(int sig_num = 0);
 
-  PlatformProcessId pid() const { return pid_; }
-  Status status() const { return status_; }
-  int returncode() const { return returncode_; }
+  PlatformProcessId pid() const { return s_.pid; }
+
+  // The accessors below are updated only after a call to Poll(), Wait() or
+  // KillAndWaitForTermination().
+  // In most cases you want to call Poll() rather than these accessors.
+
+  Status status() const { return s_.status; }
+  int returncode() const { return s_.returncode; }
 
   // This contains both stdout and stderr (if the corresponding _mode ==
   // kBuffer). It's non-const so the caller can std::move() it.
-  std::string& output() { return output_; }
+  std::string& output() { return s_.output; }
+  const ResourceUsage& rusage() const { return *s_.rusage; }
 
   Args args;
 
@@ -205,15 +226,22 @@
   void KillAtMostOnce();
   bool PollInternal(int poll_timeout_ms);
 
-  base::Pipe stdin_pipe_;
-  base::Pipe stdouterr_pipe_;
-  base::Pipe exit_status_pipe_;
-  PlatformProcessId pid_;
-  size_t input_written_ = 0;
-  Status status_ = kNotStarted;
-  int returncode_ = -1;
-  std::string output_;  // Stdin+stderr. Only when kBuffer.
-  std::thread waitpid_thread_;
+  // This is to deal robustly with the move operators, without having to
+  // manually maintain member-wise move instructions.
+  struct MovableState {
+    base::Pipe stdin_pipe;
+    base::Pipe stdouterr_pipe;
+    base::Pipe exit_status_pipe;
+    PlatformProcessId pid;
+    size_t input_written = 0;
+    Status status = kNotStarted;
+    int returncode = -1;
+    std::string output;  // Stdin+stderr. Only when kBuffer.
+    std::thread waitpid_thread;
+    std::unique_ptr<ResourceUsage> rusage;
+  };
+
+  MovableState s_;
 };
 
 }  // namespace base
diff --git a/include/perfetto/ext/base/temp_file.h b/include/perfetto/ext/base/temp_file.h
index da7e1bf..3598868 100644
--- a/include/perfetto/ext/base/temp_file.h
+++ b/include/perfetto/ext/base/temp_file.h
@@ -24,6 +24,8 @@
 namespace perfetto {
 namespace base {
 
+std::string GetSysTempDir();
+
 class TempFile {
  public:
   static TempFile CreateUnlinked();
diff --git a/include/perfetto/ext/base/thread_utils.h b/include/perfetto/ext/base/thread_utils.h
index 7869e77..26fadb9 100644
--- a/include/perfetto/ext/base/thread_utils.h
+++ b/include/perfetto/ext/base/thread_utils.h
@@ -23,7 +23,7 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #include <pthread.h>
 #include <string.h>
 #include <algorithm>
@@ -37,7 +37,7 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 // Sets the "comm" of the calling thread to the first 15 chars of the given
 // string.
 inline bool MaybeSetThreadName(const std::string& name) {
@@ -45,7 +45,7 @@
   size_t sz = std::min(name.size(), static_cast<size_t>(15));
   strncpy(buf, name.c_str(), sz);
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   return pthread_setname_np(buf) == 0;
 #else
   return pthread_setname_np(pthread_self(), buf) == 0;
diff --git a/include/perfetto/ext/base/unix_socket.h b/include/perfetto/ext/base/unix_socket.h
index df0cad8..96d4725 100644
--- a/include/perfetto/ext/base/unix_socket.h
+++ b/include/perfetto/ext/base/unix_socket.h
@@ -23,6 +23,7 @@
 #include <memory>
 #include <string>
 
+#include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
@@ -39,7 +40,18 @@
 // assuming that these enum values match the sysroot's SOCK_xxx defines rather
 // than using GetSockType() / GetSockFamily().
 enum class SockType { kStream = 100, kDgram, kSeqPacket };
-enum class SockFamily { kUnix = 200, kInet };
+enum class SockFamily { kUnix = 200, kInet, kInet6 };
+
+// Controls the getsockopt(SO_PEERCRED) behavior, which allows to obtain the
+// peer credentials.
+enum class SockPeerCredMode {
+  // Obtain the peer credentials immediatley after connection and cache them.
+  kReadOnConnect = 0,
+
+  // Don't read peer credentials at all. Calls to peer_uid()/peer_pid() will
+  // hit a DCHECK and return kInvalidUid/Pid in release builds.
+  kIgnore
+};
 
 // UnixSocketRaw is a basic wrapper around UNIX sockets. It exposes wrapper
 // methods that take care of most common pitfalls (e.g., marking fd as
@@ -150,7 +162,7 @@
 //                             | (failure or Shutdown())
 //                             V
 //                       OnDisconnect()
-class UnixSocket {
+class PERFETTO_EXPORT UnixSocket {
  public:
   class EventListener {
    public:
@@ -185,9 +197,11 @@
   // |socket_name| starts with a '@', an abstract UNIX dmoain socket will be
   // created instead of a filesystem-linked UNIX socket (Linux/Android only).
   // If SockFamily::kInet, |socket_name| is host:port (e.g., "1.2.3.4:8000").
-  // Returns always an instance. In case of failure (e.g., another socket
-  // with the same name is  already listening) the returned socket will have
-  // is_listening() == false and last_error() will contain the failure reason.
+  // If SockFamily::kInet6, |socket_name| is [host]:port (e.g., "[::1]:8000").
+  // Returns nullptr if the socket creation or bind fails. If listening fails,
+  // (e.g. if another socket with the same name is already listening) the
+  // returned socket will have is_listening() == false and last_error() will
+  // contain the failure reason.
   static std::unique_ptr<UnixSocket> Listen(const std::string& socket_name,
                                             EventListener*,
                                             TaskRunner*,
@@ -205,18 +219,22 @@
   // Creates a Unix domain socket and connects to the listening endpoint.
   // Returns always an instance. EventListener::OnConnect(bool success) will
   // be called always, whether the connection succeeded or not.
-  static std::unique_ptr<UnixSocket> Connect(const std::string& socket_name,
-                                             EventListener*,
-                                             TaskRunner*,
-                                             SockFamily,
-                                             SockType);
+  static std::unique_ptr<UnixSocket> Connect(
+      const std::string& socket_name,
+      EventListener*,
+      TaskRunner*,
+      SockFamily,
+      SockType,
+      SockPeerCredMode = SockPeerCredMode::kReadOnConnect);
 
   // Constructs a UnixSocket using the given connected socket.
-  static std::unique_ptr<UnixSocket> AdoptConnected(ScopedFile,
-                                                    EventListener*,
-                                                    TaskRunner*,
-                                                    SockFamily,
-                                                    SockType);
+  static std::unique_ptr<UnixSocket> AdoptConnected(
+      ScopedFile,
+      EventListener*,
+      TaskRunner*,
+      SockFamily,
+      SockType,
+      SockPeerCredMode = SockPeerCredMode::kReadOnConnect);
 
   UnixSocket(const UnixSocket&) = delete;
   UnixSocket& operator=(const UnixSocket&) = delete;
@@ -234,6 +252,12 @@
   // be reused with Listen() or Connect().
   void Shutdown(bool notify);
 
+  void SetTxTimeout(uint32_t timeout_ms) {
+    PERFETTO_CHECK(sock_raw_.SetTxTimeout(timeout_ms));
+  }
+  void SetRxTimeout(uint32_t timeout_ms) {
+    PERFETTO_CHECK(sock_raw_.SetRxTimeout(timeout_ms));
+  }
   // Returns true is the message was queued, false if there was no space in the
   // output buffer, in which case the client should retry or give up.
   // If any other error happens the socket will be shutdown and
@@ -278,8 +302,9 @@
   // User ID of the peer, as returned by the kernel. If the client disconnects
   // and the socket goes into the kDisconnected state, it retains the uid of
   // the last peer.
-  uid_t peer_uid() const {
-    PERFETTO_DCHECK(!is_listening() && peer_uid_ != kInvalidUid);
+  uid_t peer_uid(bool skip_check_for_testing = false) const {
+    PERFETTO_DCHECK((!is_listening() && peer_uid_ != kInvalidUid) ||
+                    skip_check_for_testing);
     ignore_result(kInvalidPid);  // Silence warnings in amalgamated builds.
     return peer_uid_;
   }
@@ -291,8 +316,9 @@
   // retains the pid of the last peer.
   //
   // This is only available on Linux / Android.
-  pid_t peer_pid() const {
-    PERFETTO_DCHECK(!is_listening() && peer_pid_ != kInvalidPid);
+  pid_t peer_pid(bool skip_check_for_testing = false) const {
+    PERFETTO_DCHECK((!is_listening() && peer_pid_ != kInvalidPid) ||
+                    skip_check_for_testing);
     return peer_pid_;
   }
 #endif
@@ -301,13 +327,18 @@
   UnixSocketRaw ReleaseSocket();
 
  private:
-  UnixSocket(EventListener*, TaskRunner*, SockFamily, SockType);
+  UnixSocket(EventListener*,
+             TaskRunner*,
+             SockFamily,
+             SockType,
+             SockPeerCredMode);
   UnixSocket(EventListener*,
              TaskRunner*,
              ScopedFile,
              State,
              SockFamily,
-             SockType);
+             SockType,
+             SockPeerCredMode);
 
   // Called once by the corresponding public static factory methods.
   void DoConnect(const std::string& socket_name);
@@ -319,6 +350,7 @@
   UnixSocketRaw sock_raw_;
   State state_ = State::kDisconnected;
   int last_error_ = 0;
+  SockPeerCredMode peer_cred_mode_ = SockPeerCredMode::kReadOnConnect;
   uid_t peer_uid_ = kInvalidUid;
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
diff --git a/include/perfetto/ext/base/unix_task_runner.h b/include/perfetto/ext/base/unix_task_runner.h
index 386c01f..8d1ab6d 100644
--- a/include/perfetto/ext/base/unix_task_runner.h
+++ b/include/perfetto/ext/base/unix_task_runner.h
@@ -25,13 +25,16 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/thread_checker.h"
 
-#include <poll.h>
 #include <chrono>
 #include <deque>
 #include <map>
 #include <mutex>
 #include <vector>
 
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <poll.h>
+#endif
+
 namespace perfetto {
 namespace base {
 
@@ -50,6 +53,8 @@
 //
 // TODO(rsavitski): consider adding a thread-check in the destructor, after
 // auditing existing usages.
+// TODO(primiano): rename this to TaskRunnerImpl. The "Unix" part is misleading
+// now as it supports also Windows.
 class UnixTaskRunner : public TaskRunner {
  public:
   UnixTaskRunner();
@@ -67,8 +72,8 @@
   // TaskRunner implementation:
   void PostTask(std::function<void()>) override;
   void PostDelayedTask(std::function<void()>, uint32_t delay_ms) override;
-  void AddFileDescriptorWatch(int fd, std::function<void()>) override;
-  void RemoveFileDescriptorWatch(int fd) override;
+  void AddFileDescriptorWatch(PlatformHandle, std::function<void()>) override;
+  void RemoveFileDescriptorWatch(PlatformHandle) override;
   bool RunsTasksOnCurrentThread() const override;
 
   // Returns true if the task runner is quitting, or has quit and hasn't been
@@ -78,22 +83,23 @@
 
  private:
   void WakeUp();
-
   void UpdateWatchTasksLocked();
-
   int GetDelayMsToNextTaskLocked() const;
   void RunImmediateAndDelayedTask();
-  void PostFileDescriptorWatches();
-  void RunFileDescriptorWatch(int fd);
+  void PostFileDescriptorWatches(uint64_t windows_wait_result);
+  void RunFileDescriptorWatch(PlatformHandle);
 
   ThreadChecker thread_checker_;
   PlatformThreadId created_thread_id_ = GetThreadId();
 
-  // On Linux, an eventfd(2) used to waking up the task runner when a new task
-  // is posted. Otherwise the read end of a pipe used for the same purpose.
   EventFd event_;
 
+// The array of fds/handles passed to poll(2) / WaitForMultipleObjects().
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  std::vector<PlatformHandle> poll_fds_;
+#else
   std::vector<struct pollfd> poll_fds_;
+#endif
 
   // --- Begin lock-protected members ---
 
@@ -105,10 +111,17 @@
 
   struct WatchTask {
     std::function<void()> callback;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    // On UNIX systems we make the FD number negative in |poll_fds_| to avoid
+    // polling it again until the queued task runs. On Windows we can't do that.
+    // Instead we keep track of its state here.
+    bool pending = false;
+#else
     size_t poll_fd_index;  // Index into |poll_fds_|.
+#endif
   };
 
-  std::map<int, WatchTask> watch_tasks_;
+  std::map<PlatformHandle, WatchTask> watch_tasks_;
   bool watch_tasks_changed_ = false;
 
   // --- End lock-protected members ---
diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h
index aadded6..cb0c3f4 100644
--- a/include/perfetto/ext/base/utils.h
+++ b/include/perfetto/ext/base/utils.h
@@ -22,10 +22,11 @@
 
 #include <errno.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdlib.h>
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <sys/types.h>
-#endif
+
+#include <atomic>
 
 #define PERFETTO_EINTR(x)                                   \
   ([&] {                                                    \
@@ -39,7 +40,7 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 // TODO(brucedawson) - create a ::perfetto::base::IOSize to replace this.
 #if defined(_WIN64)
-using ssize_t = __int64;
+using ssize_t = int64_t;
 #else
 using ssize_t = long;
 #endif
@@ -53,8 +54,18 @@
 constexpr pid_t kInvalidPid = static_cast<pid_t>(-1);
 #endif
 
+// Do not add new usages of kPageSize, consider using GetSysPageSize() below.
+// TODO(primiano): over time the semantic of kPageSize became too ambiguous.
+// Strictly speaking, this constant is incorrect on some new devices where the
+// page size can be 16K (e.g., crbug.com/1116576). Unfortunately too much code
+// ended up depending on kPageSize for purposes that are not strictly related
+// with the kernel's mm subsystem.
 constexpr size_t kPageSize = 4096;
 
+// Returns the system's page size. Use this when dealing with mmap, madvise and
+// similar mm-related syscalls.
+uint32_t GetSysPageSize();
+
 template <typename T>
 constexpr size_t ArraySize(const T& array) {
   return sizeof(array) / sizeof(array[0]);
@@ -87,6 +98,11 @@
   return err == EAGAIN || err == EWOULDBLOCK;
 }
 
+// Calls mallopt(M_PURGE, 0) on Android. Does nothing on other platforms.
+// This forces the allocator to release freed memory. This is used to work
+// around various Scudo inefficiencies. See b/170217718.
+void MaybeReleaseAllocatorMemToOS();
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/include/perfetto/ext/base/version.h
similarity index 64%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to include/perfetto/ext/base/version.h
index e22189e..212424a 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/include/perfetto/ext/base/version.h
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-syntax = "proto2";
+#ifndef INCLUDE_PERFETTO_EXT_BASE_VERSION_H_
+#define INCLUDE_PERFETTO_EXT_BASE_VERSION_H_
 
-package perfetto.protos;
+namespace perfetto {
+namespace base {
 
-import "protos/perfetto/metrics/custom_options.proto";
+// The returned pointer is a static string is safe to pass around.
+const char* GetVersionString();
 
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
-}
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_VERSION_H_
diff --git a/include/perfetto/ext/ipc/client.h b/include/perfetto/ext/ipc/client.h
index 72c3e73..39e8bec 100644
--- a/include/perfetto/ext/ipc/client.h
+++ b/include/perfetto/ext/ipc/client.h
@@ -44,8 +44,26 @@
 // });
 class Client {
  public:
-  static std::unique_ptr<Client> CreateInstance(const char* socket_name,
-                                                base::TaskRunner*);
+  // struct ConnArgs is used for creating a client in 2 connection modes:
+  // 1. Connect using a socket name with the option to retry the connection on
+  //    connection failure.
+  // 2. Adopt a connected socket.
+  struct ConnArgs {
+    ConnArgs(const char* sock_name, bool sock_retry)
+        : socket_name(sock_name), retry(sock_retry) {}
+    explicit ConnArgs(base::ScopedFile sock_fd)
+        : socket_fd(std::move(sock_fd)) {}
+
+    // Disallow copy. Only supports move.
+    ConnArgs(const ConnArgs& other) = delete;
+    ConnArgs(ConnArgs&& other) = default;
+
+    base::ScopedFile socket_fd;
+    const char* socket_name = nullptr;
+    bool retry = false;  // Only for connecting with |socket_name|.
+  };
+
+  static std::unique_ptr<Client> CreateInstance(ConnArgs, base::TaskRunner*);
   virtual ~Client();
 
   virtual void BindService(base::WeakPtr<ServiceProxy>) = 0;
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/BUILD.gn b/include/perfetto/ext/trace_processor/importers/memory_tracker/BUILD.gn
new file mode 100644
index 0000000..7d093f4
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/BUILD.gn
@@ -0,0 +1,31 @@
+# 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.
+
+import("../../../../../../gn/perfetto.gni")
+
+source_set("memory_tracker") {
+  deps = [ "../../../../../../gn:default_deps" ]
+  public_deps = [
+    "../../../../base",
+    "../../../base",
+  ]
+  sources = [
+    "graph.h",
+    "graph_processor.h",
+    "memory_allocator_node_id.h",
+    "memory_graph_edge.h",
+    "raw_memory_graph_node.h",
+    "raw_process_memory_node.h",
+  ]
+}
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/graph.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/graph.h
new file mode 100644
index 0000000..fccf57c
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/graph.h
@@ -0,0 +1,316 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_H_
+
+#include <sys/types.h>
+
+#include <forward_list>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/export.h"
+#include "perfetto/base/proc_utils.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+const base::PlatformProcessId kNullProcessId = 0;
+
+// Contains processed node graphs for each process and in the global space.
+// This class is also the arena which owns the nodes of the graph.
+class PERFETTO_EXPORT GlobalNodeGraph {
+ public:
+  class Node;
+  class Edge;
+  class PreOrderIterator;
+  class PostOrderIterator;
+
+  // Graph of nodes either associated with a process or with
+  // the shared space.
+  class PERFETTO_EXPORT Process {
+   public:
+    Process(base::PlatformProcessId pid, GlobalNodeGraph* global_graph);
+    ~Process();
+
+    // Creates a node in the node graph which is associated with the
+    // given |id|, |path| and |weak|ness and returns it.
+    GlobalNodeGraph::Node* CreateNode(MemoryAllocatorNodeId id,
+                                      const std::string& path,
+                                      bool weak);
+
+    // Returns the node in the graph at the given |path| or nullptr
+    // if no such node exists in the provided |graph|.
+    GlobalNodeGraph::Node* FindNode(const std::string& path);
+
+    base::PlatformProcessId pid() const { return pid_; }
+    GlobalNodeGraph* global_graph() const { return global_graph_; }
+    GlobalNodeGraph::Node* root() const { return root_; }
+
+   private:
+    base::PlatformProcessId pid_;
+    GlobalNodeGraph* global_graph_;
+    GlobalNodeGraph::Node* root_;
+    Process(const Process&) = delete;
+    Process& operator=(const Process&) = delete;
+  };
+
+  // A single node in the graph of allocator nodes associated with a
+  // certain path and containing the entries for this path.
+  class PERFETTO_EXPORT Node {
+   public:
+    // Auxilary data (a scalar number or a string) about this node each
+    // associated with a key.
+    struct PERFETTO_EXPORT Entry {
+      enum Type {
+        kUInt64,
+        kString,
+      };
+
+      // The units of the entry if the entry is a scalar. The scalar
+      // refers to either a number of objects or a size in bytes.
+      enum ScalarUnits {
+        kObjects,
+        kBytes,
+      };
+
+      // Creates the entry with the appropriate type.
+      Entry(ScalarUnits units, uint64_t value);
+      explicit Entry(const std::string& value);
+
+      const Type type;
+      const ScalarUnits units;
+
+      // The value of the entry if this entry has a string type.
+      const std::string value_string;
+
+      // The value of the entry if this entry has a integer type.
+      const uint64_t value_uint64;
+    };
+
+    explicit Node(GlobalNodeGraph::Process* node_graph, Node* parent);
+    ~Node();
+
+    // Gets the direct child of a node for the given |subpath|.
+    Node* GetChild(const std::string& name) const;
+
+    // Inserts the given |node| as a child of the current node
+    // with the given |subpath| as the key.
+    void InsertChild(const std::string& name, Node* node);
+
+    // Creates a child for this node with the given |name| as the key.
+    Node* CreateChild(const std::string& name);
+
+    // Checks if the current node is a descendent (i.e. exists as a child,
+    // child of a child, etc.) of the given node |possible_parent|.
+    bool IsDescendentOf(const Node& possible_parent) const;
+
+    // Adds an entry for this node node with the given |name|, |units| and
+    // type.
+    void AddEntry(const std::string& name,
+                  Entry::ScalarUnits units,
+                  uint64_t value);
+    void AddEntry(const std::string& name, const std::string& value);
+
+    // Adds an edge which indicates that this node is owned by
+    // another node.
+    void AddOwnedByEdge(Edge* edge);
+
+    // Sets the edge indicates that this node owns another node.
+    void SetOwnsEdge(Edge* edge);
+
+    bool is_weak() const { return weak_; }
+    void set_weak(bool weak) { weak_ = weak; }
+    bool is_explicit() const { return explicit_; }
+    void set_explicit(bool explicit_node) { explicit_ = explicit_node; }
+    uint64_t not_owned_sub_size() const { return not_owned_sub_size_; }
+    void add_not_owned_sub_size(uint64_t addition) {
+      not_owned_sub_size_ += addition;
+    }
+    uint64_t not_owning_sub_size() const { return not_owning_sub_size_; }
+    void add_not_owning_sub_size(uint64_t addition) {
+      not_owning_sub_size_ += addition;
+    }
+    double owned_coefficient() const { return owned_coefficient_; }
+    void set_owned_coefficient(double owned_coefficient) {
+      owned_coefficient_ = owned_coefficient;
+    }
+    double owning_coefficient() const { return owning_coefficient_; }
+    void set_owning_coefficient(double owning_coefficient) {
+      owning_coefficient_ = owning_coefficient;
+    }
+    double cumulative_owned_coefficient() const {
+      return cumulative_owned_coefficient_;
+    }
+    void set_cumulative_owned_coefficient(double cumulative_owned_coefficient) {
+      cumulative_owned_coefficient_ = cumulative_owned_coefficient;
+    }
+    double cumulative_owning_coefficient() const {
+      return cumulative_owning_coefficient_;
+    }
+    void set_cumulative_owning_coefficient(
+        double cumulative_owning_coefficient) {
+      cumulative_owning_coefficient_ = cumulative_owning_coefficient;
+    }
+    MemoryAllocatorNodeId id() const { return id_; }
+    void set_id(MemoryAllocatorNodeId id) { id_ = id; }
+    GlobalNodeGraph::Edge* owns_edge() const { return owns_edge_; }
+    std::map<std::string, Node*>* children() { return &children_; }
+    const std::map<std::string, Node*>& const_children() const {
+      return children_;
+    }
+    std::vector<GlobalNodeGraph::Edge*>* owned_by_edges() {
+      return &owned_by_edges_;
+    }
+    const Node* parent() const { return parent_; }
+    const GlobalNodeGraph::Process* node_graph() const { return node_graph_; }
+    std::map<std::string, Entry>* entries() { return &entries_; }
+    const std::map<std::string, Entry>& const_entries() const {
+      return entries_;
+    }
+
+   private:
+    GlobalNodeGraph::Process* node_graph_;
+    Node* const parent_;
+    MemoryAllocatorNodeId id_;
+    std::map<std::string, Entry> entries_;
+    std::map<std::string, Node*> children_;
+    bool explicit_ = false;
+    bool weak_ = false;
+    uint64_t not_owning_sub_size_ = 0;
+    uint64_t not_owned_sub_size_ = 0;
+    double owned_coefficient_ = 1;
+    double owning_coefficient_ = 1;
+    double cumulative_owned_coefficient_ = 1;
+    double cumulative_owning_coefficient_ = 1;
+
+    GlobalNodeGraph::Edge* owns_edge_;
+    std::vector<GlobalNodeGraph::Edge*> owned_by_edges_;
+
+    Node(const Node&) = delete;
+    Node& operator=(const Node&) = delete;
+  };
+
+  // An edge in the node graph which indicates ownership between the
+  // source and target nodes.
+  class PERFETTO_EXPORT Edge {
+   public:
+    Edge(GlobalNodeGraph::Node* source,
+         GlobalNodeGraph::Node* target,
+         int priority);
+
+    GlobalNodeGraph::Node* source() const { return source_; }
+    GlobalNodeGraph::Node* target() const { return target_; }
+    int priority() const { return priority_; }
+
+   private:
+    GlobalNodeGraph::Node* const source_;
+    GlobalNodeGraph::Node* const target_;
+    const int priority_;
+  };
+
+  // An iterator-esque class which yields nodes in a depth-first pre order.
+  class PERFETTO_EXPORT PreOrderIterator {
+   public:
+    explicit PreOrderIterator(std::vector<Node*>&& root_nodes);
+    PreOrderIterator(PreOrderIterator&& other);
+    ~PreOrderIterator();
+
+    // Yields the next node in the DFS post-order traversal.
+    Node* next();
+
+   private:
+    std::vector<Node*> to_visit_;
+    std::set<const Node*> visited_;
+  };
+
+  // An iterator-esque class which yields nodes in a depth-first post order.
+  class PERFETTO_EXPORT PostOrderIterator {
+   public:
+    explicit PostOrderIterator(std::vector<Node*>&& root_nodes);
+    PostOrderIterator(PostOrderIterator&& other);
+    ~PostOrderIterator();
+
+    // Yields the next node in the DFS post-order traversal.
+    Node* next();
+
+   private:
+    std::vector<Node*> to_visit_;
+    std::set<Node*> visited_;
+    std::vector<Node*> path_;
+  };
+
+  using ProcessNodeGraphMap =
+      std::map<base::PlatformProcessId,
+               std::unique_ptr<GlobalNodeGraph::Process>>;
+  using IdNodeMap = std::map<MemoryAllocatorNodeId, Node*>;
+
+  GlobalNodeGraph();
+  ~GlobalNodeGraph();
+
+  // Creates a container for all the node graphs for the process given
+  // by the given |process_id|.
+  GlobalNodeGraph::Process* CreateGraphForProcess(
+      base::PlatformProcessId process_id);
+
+  // Adds an edge in the node graph with the given source and target nodes
+  // and edge priority.
+  void AddNodeOwnershipEdge(Node* owner, Node* owned, int priority);
+
+  // Returns an iterator which yields nodes in the nodes in this graph in
+  // pre-order. That is, children and owners of nodes are returned after the
+  // node itself.
+  PreOrderIterator VisitInDepthFirstPreOrder();
+
+  // Returns an iterator which yields nodes in the nodes in this graph in
+  // post-order. That is, children and owners of nodes are returned before the
+  // node itself.
+  PostOrderIterator VisitInDepthFirstPostOrder();
+
+  const IdNodeMap& nodes_by_id() const { return nodes_by_id_; }
+  GlobalNodeGraph::Process* shared_memory_graph() const {
+    return shared_memory_graph_.get();
+  }
+  const ProcessNodeGraphMap& process_node_graphs() const {
+    return process_node_graphs_;
+  }
+  const std::forward_list<Edge>& edges() const { return all_edges_; }
+
+ private:
+  // Creates a node in the arena which is associated with the given
+  // |node_graph| and for the given |parent|.
+  Node* CreateNode(GlobalNodeGraph::Process* node_graph,
+                   GlobalNodeGraph::Node* parent);
+
+  std::forward_list<Node> all_nodes_;
+  std::forward_list<Edge> all_edges_;
+  IdNodeMap nodes_by_id_;
+  std::unique_ptr<GlobalNodeGraph::Process> shared_memory_graph_;
+  ProcessNodeGraphMap process_node_graphs_;
+  GlobalNodeGraph(const GlobalNodeGraph&) = delete;
+  GlobalNodeGraph& operator=(const GlobalNodeGraph&) = delete;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_H_
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h
new file mode 100644
index 0000000..c9fea6b
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h
@@ -0,0 +1,250 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_PROCESSOR_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_PROCESSOR_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "perfetto/base/proc_utils.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class PERFETTO_EXPORT GraphProcessor {
+ public:
+  // This map does not own the pointers inside.
+  using RawMemoryNodeMap =
+      std::map<base::PlatformProcessId, std::unique_ptr<RawProcessMemoryNode>>;
+
+  static std::unique_ptr<GlobalNodeGraph> CreateMemoryGraph(
+      const RawMemoryNodeMap& process_nodes);
+
+  static void RemoveWeakNodesFromGraph(GlobalNodeGraph* global_graph);
+
+  static void AddOverheadsAndPropagateEntries(GlobalNodeGraph* global_graph);
+
+  static void CalculateSizesForGraph(GlobalNodeGraph* global_graph);
+
+  static std::map<base::PlatformProcessId, uint64_t>
+  ComputeSharedFootprintFromGraph(const GlobalNodeGraph& global_graph);
+
+ private:
+  friend class GraphProcessorTest;
+
+  static void CollectAllocatorNodes(const RawProcessMemoryNode& source,
+                                    GlobalNodeGraph* global_graph,
+                                    GlobalNodeGraph::Process* process_graph);
+
+  static void AddEdges(const RawProcessMemoryNode& source,
+                       GlobalNodeGraph* global_graph);
+
+  static void MarkImplicitWeakParentsRecursively(GlobalNodeGraph::Node* node);
+
+  static void MarkWeakOwnersAndChildrenRecursively(
+      GlobalNodeGraph::Node* node,
+      std::set<const GlobalNodeGraph::Node*>* nodes);
+
+  static void RemoveWeakNodesRecursively(GlobalNodeGraph::Node* parent);
+
+  static void AssignTracingOverhead(const std::string& allocator,
+                                    GlobalNodeGraph* global_graph,
+                                    GlobalNodeGraph::Process* process);
+
+  static GlobalNodeGraph::Node::Entry AggregateNumericWithNameForNode(
+      GlobalNodeGraph::Node* node,
+      const std::string& name);
+
+  static void AggregateNumericsRecursively(GlobalNodeGraph::Node* node);
+
+  static void PropagateNumericsAndDiagnosticsRecursively(
+      GlobalNodeGraph::Node* node);
+
+  static base::Optional<uint64_t> AggregateSizeForDescendantNode(
+      GlobalNodeGraph::Node* root,
+      GlobalNodeGraph::Node* descendant);
+
+  static void CalculateSizeForNode(GlobalNodeGraph::Node* node);
+
+  /**
+   * Calculate not-owned and not-owning sub-sizes of a memory allocator node
+   * from its children's (sub-)sizes.
+   *
+   * Not-owned sub-size refers to the aggregated memory of all children which
+   * is not owned by other MADs. Conversely, not-owning sub-size is the
+   * aggregated memory of all children which do not own another MAD. The
+   * diagram below illustrates these two concepts:
+   *
+   *     ROOT 1                         ROOT 2
+   *     size: 4                        size: 5
+   *     not-owned sub-size: 4          not-owned sub-size: 1 (!)
+   *     not-owning sub-size: 0 (!)     not-owning sub-size: 5
+   *
+   *      ^                              ^
+   *      |                              |
+   *
+   *     PARENT 1   ===== owns =====>   PARENT 2
+   *     size: 4                        size: 5
+   *     not-owned sub-size: 4          not-owned sub-size: 5
+   *     not-owning sub-size: 4         not-owning sub-size: 5
+   *
+   *      ^                              ^
+   *      |                              |
+   *
+   *     CHILD 1                        CHILD 2
+   *     size [given]: 4                size [given]: 5
+   *     not-owned sub-size: 4          not-owned sub-size: 5
+   *     not-owning sub-size: 4         not-owning sub-size: 5
+   *
+   * This method assumes that (1) the size of the node, its children, and its
+   * owners [see calculateSizes()] and (2) the not-owned and not-owning
+   * sub-sizes of both the children and owners of the node have already been
+   * calculated [depth-first post-order traversal].
+   */
+  static void CalculateNodeSubSizes(GlobalNodeGraph::Node* node);
+
+  /**
+   * Calculate owned and owning coefficients of a memory allocator node and
+   * its owners.
+   *
+   * The owning coefficient refers to the proportion of a node's not-owning
+   * sub-size which is attributed to the node (only relevant to owning MADs).
+   * Conversely, the owned coefficient is the proportion of a node's
+   * not-owned sub-size, which is attributed to it (only relevant to owned
+   * MADs).
+   *
+   * The not-owned size of the owned node is split among its owners in the
+   * order of the ownership importance as demonstrated by the following
+   * example:
+   *
+   *                                          memory allocator nodes
+   *                                   OWNED  OWNER1  OWNER2  OWNER3 OWNER4
+   *       not-owned sub-size [given]     10       -       -       - -
+   *      not-owning sub-size [given]      -       6       7       5 8
+   *               importance [given]      -       2       2       1 0
+   *    attributed not-owned sub-size      2       -       -       - -
+   *   attributed not-owning sub-size      -       3       4       0 1
+   *                owned coefficient   2/10       -       -       - -
+   *               owning coefficient      -     3/6     4/7     0/5 1/8
+   *
+   * Explanation: Firstly, 6 bytes are split equally among OWNER1 and OWNER2
+   * (highest importance). OWNER2 owns one more byte, so its attributed
+   * not-owning sub-size is 6/2 + 1 = 4 bytes. OWNER3 is attributed no size
+   * because it is smaller than the owners with higher priority. However,
+   * OWNER4 is larger, so it's attributed the difference 8 - 7 = 1 byte.
+   * Finally, 2 bytes remain unattributed and are hence kept in the OWNED
+   * node as attributed not-owned sub-size. The coefficients are then
+   * directly calculated as fractions of the sub-sizes and corresponding
+   * attributed sub-sizes.
+   *
+   * Note that we always assume that all ownerships of a node overlap (e.g.
+   * OWNER3 is subsumed by both OWNER1 and OWNER2). Hence, the table could
+   * be alternatively represented as follows:
+   *
+   *                                 owned memory range
+   *              0   1   2    3    4    5    6        7        8   9  10
+   *   Priority 2 |  OWNER1 + OWNER2 (split)  | OWNER2 |
+   *   Priority 1 | (already attributed) |
+   *   Priority 0 | - - -  (already attributed)  - - - | OWNER4 |
+   *    Remainder | - - - - - (already attributed) - - - - - -  | OWNED |
+   *
+   * This method assumes that (1) the size of the node [see calculateSizes()]
+   * and (2) the not-owned size of the node and not-owning sub-sizes of its
+   * owners [see the first step of calculateEffectiveSizes()] have already
+   * been calculated. Note that the method doesn't make any assumptions about
+   * the order in which nodes are visited.
+   */
+  static void CalculateNodeOwnershipCoefficient(GlobalNodeGraph::Node* node);
+
+  /**
+   * Calculate cumulative owned and owning coefficients of a memory allocator
+   * node from its (non-cumulative) owned and owning coefficients and the
+   * cumulative coefficients of its parent and/or owned node.
+   *
+   * The cumulative coefficients represent the total effect of all
+   * (non-strict) ancestor ownerships on a memory allocator node. The
+   * cumulative owned coefficient of a MAD can be calculated simply as:
+   *
+   *   cumulativeOwnedC(M) = ownedC(M) * cumulativeOwnedC(parent(M))
+   *
+   * This reflects the assumption that if a parent of a child MAD is
+   * (partially) owned, then the parent's owner also indirectly owns (a part
+   * of) the child MAD.
+   *
+   * The cumulative owning coefficient of a MAD depends on whether the MAD
+   * owns another node:
+   *
+   *                           [if M doesn't own another MAD]
+   *                         / cumulativeOwningC(parent(M))
+   *   cumulativeOwningC(M) =
+   *                         \ [if M owns another MAD]
+   *                           owningC(M) * cumulativeOwningC(owned(M))
+   *
+   * The reasoning behind the first case is similar to the one for cumulative
+   * owned coefficient above. The only difference is that we don't need to
+   * include the node's (non-cumulative) owning coefficient because it is
+   * implicitly 1.
+   *
+   * The formula for the second case is derived as follows: Since the MAD
+   * owns another node, its memory is not included in its parent's not-owning
+   * sub-size and hence shouldn't be affected by the parent's corresponding
+   * cumulative coefficient. Instead, the MAD indirectly owns everything
+   * owned by its owned node (and so it should be affected by the
+   * corresponding coefficient).
+   *
+   * Note that undefined coefficients (and coefficients of non-existent
+   * nodes) are implicitly assumed to be 1.
+   *
+   * This method assumes that (1) the size of the node [see calculateSizes()],
+   * (2) the (non-cumulative) owned and owning coefficients of the node [see
+   * the second step of calculateEffectiveSizes()], and (3) the cumulative
+   * coefficients of the node's parent and owned MADs (if present)
+   * [depth-first pre-order traversal] have already been calculated.
+   */
+  static void CalculateNodeCumulativeOwnershipCoefficient(
+      GlobalNodeGraph::Node* node);
+
+  /**
+   * Calculate the effective size of a memory allocator node.
+   *
+   * In order to simplify the (already complex) calculation, we use the fact
+   * that effective size is cumulative (unlike regular size), i.e. the
+   * effective size of a non-leaf node is equal to the sum of effective sizes
+   * of its children. The effective size of a leaf MAD is calculated as:
+   *
+   *   effectiveSize(M) = size(M) * cumulativeOwningC(M) * cumulativeOwnedC(M)
+   *
+   * This method assumes that (1) the size of the node and its children [see
+   * calculateSizes()] and (2) the cumulative owning and owned coefficients
+   * of the node (if it's a leaf node) [see the third step of
+   * calculateEffectiveSizes()] or the effective sizes of its children (if
+   * it's a non-leaf node) [depth-first post-order traversal] have already
+   * been calculated.
+   */
+  static void CalculateNodeEffectiveSize(GlobalNodeGraph::Node* node);
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_GRAPH_PROCESSOR_H_
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h
new file mode 100644
index 0000000..f4899d5
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h
@@ -0,0 +1,62 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_ALLOCATOR_NODE_ID_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_ALLOCATOR_NODE_ID_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "perfetto/base/export.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class PERFETTO_EXPORT MemoryAllocatorNodeId {
+ public:
+  MemoryAllocatorNodeId();
+  explicit MemoryAllocatorNodeId(uint64_t id);
+
+  uint64_t ToUint64() const { return id_; }
+
+  // Returns a (hex-encoded) string representation of the id.
+  std::string ToString() const;
+
+  bool empty() const { return id_ == 0u; }
+
+  bool operator==(const MemoryAllocatorNodeId& other) const {
+    return id_ == other.id_;
+  }
+
+  bool operator!=(const MemoryAllocatorNodeId& other) const {
+    return !(*this == other);
+  }
+
+  bool operator<(const MemoryAllocatorNodeId& other) const {
+    return id_ < other.id_;
+  }
+
+ private:
+  uint64_t id_;
+
+  // Deliberately copy-able.
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_ALLOCATOR_NODE_ID_H_
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h
new file mode 100644
index 0000000..6f7d4fe
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h
@@ -0,0 +1,55 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_GRAPH_EDGE_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_GRAPH_EDGE_H_
+
+#include <stdint.h>
+
+#include "perfetto/base/export.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class PERFETTO_EXPORT MemoryGraphEdge {
+ public:
+  MemoryGraphEdge(MemoryAllocatorNodeId s,
+                  MemoryAllocatorNodeId t,
+                  int i,
+                  bool o)
+      : source(s), target(t), importance(i), overridable(o) {}
+
+  MemoryGraphEdge& operator=(const MemoryGraphEdge& edge) {
+    source = edge.source;
+    target = edge.target;
+    importance = edge.importance;
+    overridable = edge.overridable;
+    return *this;
+  }
+
+  MemoryAllocatorNodeId source;
+  MemoryAllocatorNodeId target;
+  int importance;
+  bool overridable;
+
+  // Deliberately copy-able.
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_MEMORY_GRAPH_EDGE_H_
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h
new file mode 100644
index 0000000..e865afe
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h
@@ -0,0 +1,147 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_MEMORY_GRAPH_NODE_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_MEMORY_GRAPH_NODE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/export.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Describes the level of detail of the memory graph.
+enum class LevelOfDetail : uint32_t {
+  kFirst,
+
+  // For background tracing mode. The node time is quick, and typically just the
+  // totals are expected. Suballocations need not be specified. Node name must
+  // contain only pre-defined strings and string arguments cannot be added.
+  kBackground = kFirst,
+
+  // For the levels below, MemoryNodeProvider instances must guarantee that the
+  // total size reported in the root node is consistent. Only the granularity of
+  // the child MemoryAllocatorNode(s) differs with the levels.
+
+  // Few entries, typically a fixed number, per node.
+  kLight,
+
+  // Unrestricted amount of entries per node.
+  kDetailed,
+
+  kLast = kDetailed
+};
+
+// Data model for user-land memory nodes.
+class PERFETTO_EXPORT RawMemoryGraphNode {
+ public:
+  enum Flags {
+    kDefault = 0,
+
+    // A node marked weak will be discarded if there is no ownership edge exists
+    // from a non-weak node.
+    kWeak = 1 << 0,
+  };
+
+  // In the UI table each MemoryAllocatorNode becomes
+  // a row and each Entry generates a column (if it doesn't already
+  // exist).
+  struct PERFETTO_EXPORT MemoryNodeEntry {
+    enum EntryType {
+      kUint64,
+      kString,
+    };
+
+    MemoryNodeEntry(const std::string& name,
+                    const std::string& units,
+                    uint64_t value);
+    MemoryNodeEntry(const std::string& name,
+                    const std::string& units,
+                    const std::string& value);
+
+    bool operator==(const MemoryNodeEntry& rhs) const;
+
+    std::string name;
+    std::string units;
+
+    EntryType entry_type;
+
+    uint64_t value_uint64;
+    std::string value_string;
+  };
+
+  RawMemoryGraphNode(const std::string& absolute_name,
+                     LevelOfDetail level,
+                     MemoryAllocatorNodeId id);
+
+  RawMemoryGraphNode(
+      const std::string& absolute_name,
+      LevelOfDetail level,
+      MemoryAllocatorNodeId id,
+      std::vector<RawMemoryGraphNode::MemoryNodeEntry>&& entries);
+
+  // Standard attribute |name|s for the AddScalar and AddString() methods.
+  static const char kNameSize[];         // To represent allocated space.
+  static const char kNameObjectCount[];  // To represent number of objects.
+
+  // Standard attribute |unit|s for the AddScalar and AddString() methods.
+  static const char kUnitsBytes[];    // Unit name to represent bytes.
+  static const char kUnitsObjects[];  // Unit name to represent #objects.
+
+  // Constants used only internally and by tests.
+  static const char kTypeScalar[];  // Type name for scalar attributes.
+  static const char kTypeString[];  // Type name for string attributes.
+
+  // |id| is an optional global node identifier, unique across all processes
+  // within the scope of a global node.
+  // Subsequent MemoryAllocatorNode(s) with the same |absolute_name| are
+  // expected to have the same id.
+  MemoryAllocatorNodeId id() const { return id_; }
+
+  // Absolute name, unique within the scope of an entire ProcessMemoryNode.
+  const std::string& absolute_name() const { return absolute_name_; }
+
+  const std::vector<MemoryNodeEntry>& entries() const { return entries_; }
+
+  LevelOfDetail level_of_detail() const { return level_of_detail_; }
+
+  // Use enum Flags to set values.
+  void set_flags(int flags) { flags_ |= flags; }
+  void clear_flags(int flags) { flags_ &= ~flags; }
+  int flags() const { return flags_; }
+
+ private:
+  std::string absolute_name_;
+  LevelOfDetail level_of_detail_;
+  std::vector<MemoryNodeEntry> entries_;
+  MemoryAllocatorNodeId id_;
+
+  // A node marked weak will be discarded by TraceViewer.
+  int flags_;  // See enum Flags.
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_MEMORY_GRAPH_NODE_H_
diff --git a/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h b/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h
new file mode 100644
index 0000000..66b4e89
--- /dev/null
+++ b/include/perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h
@@ -0,0 +1,86 @@
+/*
+ * 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 INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_PROCESS_MEMORY_NODE_H_
+#define INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_PROCESS_MEMORY_NODE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/base/export.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_graph_edge.h"
+#include "perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// ProcessMemoryNode is as a strongly typed container which holds the nodes
+// produced by the MemoryNodeProvider(s) for a specific process.
+class PERFETTO_EXPORT RawProcessMemoryNode {
+ public:
+  // Maps allocator nodes absolute names (allocator_name/heap/subheap) to
+  // MemoryAllocatorNode instances.
+  using MemoryNodesMap =
+      std::map<std::string, std::unique_ptr<RawMemoryGraphNode>>;
+
+  // Stores allocator node edges indexed by source allocator node GUID.
+  using AllocatorNodeEdgesMap =
+      std::map<MemoryAllocatorNodeId, std::unique_ptr<MemoryGraphEdge>>;
+
+  explicit RawProcessMemoryNode(
+      LevelOfDetail level_of_detail,
+      AllocatorNodeEdgesMap&& edges_map = AllocatorNodeEdgesMap{},
+      MemoryNodesMap&& nodes_map = MemoryNodesMap{});
+  RawProcessMemoryNode(RawProcessMemoryNode&&);
+  ~RawProcessMemoryNode();
+  RawProcessMemoryNode& operator=(RawProcessMemoryNode&&);
+
+  // Looks up a MemoryAllocatorNode given its allocator and heap names, or
+  // nullptr if not found.
+  RawMemoryGraphNode* GetAllocatorNode(const std::string& absolute_name) const;
+
+  // Returns the map of the MemoryAllocatorNodes added to this node.
+  const MemoryNodesMap& allocator_nodes() const { return allocator_nodes_; }
+
+  const AllocatorNodeEdgesMap& allocator_nodes_edges() const {
+    return allocator_nodes_edges_;
+  }
+
+  const LevelOfDetail& level_of_detail() const { return level_of_detail_; }
+
+ private:
+  LevelOfDetail level_of_detail_;
+
+  // Keeps track of relationships between MemoryAllocatorNode(s).
+  AllocatorNodeEdgesMap allocator_nodes_edges_;
+
+  // Level of detail of the current node.
+  MemoryNodesMap allocator_nodes_;
+
+  // This class is uncopyable and unassignable.
+  RawProcessMemoryNode(const RawProcessMemoryNode&) = delete;
+  RawProcessMemoryNode& operator=(const RawProcessMemoryNode&) = delete;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACE_PROCESSOR_IMPORTERS_MEMORY_TRACKER_RAW_PROCESS_MEMORY_NODE_H_
diff --git a/include/perfetto/ext/traced/sys_stats_counters.h b/include/perfetto/ext/traced/sys_stats_counters.h
index f4ba962..fa846d6 100644
--- a/include/perfetto/ext/traced/sys_stats_counters.h
+++ b/include/perfetto/ext/traced/sys_stats_counters.h
@@ -208,6 +208,57 @@
     {"nr_zspages", protos::pbzero::VmstatCounters::VMSTAT_NR_ZSPAGES},
     {"nr_ion_heap", protos::pbzero::VmstatCounters::VMSTAT_NR_ION_HEAP},
     {"nr_gpu_heap", protos::pbzero::VmstatCounters::VMSTAT_NR_GPU_HEAP},
+    {"allocstall_dma", protos::pbzero::VmstatCounters::VMSTAT_ALLOCSTALL_DMA},
+    {"allocstall_movable",
+     protos::pbzero::VmstatCounters::VMSTAT_ALLOCSTALL_MOVABLE},
+    {"allocstall_normal",
+     protos::pbzero::VmstatCounters::VMSTAT_ALLOCSTALL_NORMAL},
+    {"compact_daemon_free_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_DAEMON_FREE_SCANNED},
+    {"compact_daemon_migrate_scanned",
+     protos::pbzero::VmstatCounters::VMSTAT_COMPACT_DAEMON_MIGRATE_SCANNED},
+    {"nr_fastrpc", protos::pbzero::VmstatCounters::VMSTAT_NR_FASTRPC},
+    {"nr_indirectly_reclaimable",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_INDIRECTLY_RECLAIMABLE},
+    {"nr_ion_heap_pool",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ION_HEAP_POOL},
+    {"nr_kernel_misc_reclaimable",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_KERNEL_MISC_RECLAIMABLE},
+    {"nr_shadow_call_stack_bytes",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_SHADOW_CALL_STACK_BYTES},
+    {"nr_shmem_hugepages",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_SHMEM_HUGEPAGES},
+    {"nr_shmem_pmdmapped",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_SHMEM_PMDMAPPED},
+    {"nr_unreclaimable_pages",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_UNRECLAIMABLE_PAGES},
+    {"nr_zone_active_anon",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_ACTIVE_ANON},
+    {"nr_zone_active_file",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_ACTIVE_FILE},
+    {"nr_zone_inactive_anon",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_INACTIVE_ANON},
+    {"nr_zone_inactive_file",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_INACTIVE_FILE},
+    {"nr_zone_unevictable",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_UNEVICTABLE},
+    {"nr_zone_write_pending",
+     protos::pbzero::VmstatCounters::VMSTAT_NR_ZONE_WRITE_PENDING},
+    {"oom_kill", protos::pbzero::VmstatCounters::VMSTAT_OOM_KILL},
+    {"pglazyfree", protos::pbzero::VmstatCounters::VMSTAT_PGLAZYFREE},
+    {"pglazyfreed", protos::pbzero::VmstatCounters::VMSTAT_PGLAZYFREED},
+    {"pgrefill", protos::pbzero::VmstatCounters::VMSTAT_PGREFILL},
+    {"pgscan_direct", protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_DIRECT},
+    {"pgscan_kswapd", protos::pbzero::VmstatCounters::VMSTAT_PGSCAN_KSWAPD},
+    {"pgskip_dma", protos::pbzero::VmstatCounters::VMSTAT_PGSKIP_DMA},
+    {"pgskip_movable", protos::pbzero::VmstatCounters::VMSTAT_PGSKIP_MOVABLE},
+    {"pgskip_normal", protos::pbzero::VmstatCounters::VMSTAT_PGSKIP_NORMAL},
+    {"pgsteal_direct", protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_DIRECT},
+    {"pgsteal_kswapd", protos::pbzero::VmstatCounters::VMSTAT_PGSTEAL_KSWAPD},
+    {"swap_ra_hit", protos::pbzero::VmstatCounters::VMSTAT_SWAP_RA_HIT},
+    {"swap_ra", protos::pbzero::VmstatCounters::VMSTAT_SWAP_RA},
+    {"workingset_restore",
+     protos::pbzero::VmstatCounters::VMSTAT_WORKINGSET_RESTORE},
 };
 
 // Returns a lookup table of meminfo counter names addressable by counter id.
diff --git a/include/perfetto/ext/tracing/core/consumer.h b/include/perfetto/ext/tracing/core/consumer.h
index 0cbd158..fce7c65 100644
--- a/include/perfetto/ext/tracing/core/consumer.h
+++ b/include/perfetto/ext/tracing/core/consumer.h
@@ -47,8 +47,9 @@
   // - The consumer explicitly called DisableTracing()
   // - The TraceConfig's |duration_ms| has been reached.
   // - The TraceConfig's |max_file_size_bytes| has been reached.
-  // - An error occurred while trying to enable tracing.
-  virtual void OnTracingDisabled() = 0;
+  // - An error occurred while trying to enable tracing. In this case |error|
+  //   is non-empty.
+  virtual void OnTracingDisabled(const std::string& error) = 0;
 
   // Called back by the Service (or transport layer) after invoking
   // TracingService::ConsumerEndpoint::ReadBuffers(). This function can be
diff --git a/include/perfetto/ext/tracing/core/shared_memory_abi.h b/include/perfetto/ext/tracing/core/shared_memory_abi.h
index 9d17850..567a295 100644
--- a/include/perfetto/ext/tracing/core/shared_memory_abi.h
+++ b/include/perfetto/ext/tracing/core/shared_memory_abi.h
@@ -143,6 +143,8 @@
 
 class SharedMemoryABI {
  public:
+  static constexpr size_t kMinPageSize = 4 * 1024;
+
   // This is due to Chunk::size being 16 bits.
   static constexpr size_t kMaxPageSize = 64 * 1024;
 
@@ -413,7 +415,7 @@
     }
 
     // Flags are cleared by TryAcquireChunk(), by passing the new header for
-    // the chunk.
+    // the chunk, or through ClearNeedsPatchingFlag.
     void SetFlag(ChunkHeader::Flags flag) {
       ChunkHeader* chunk_header = header();
       auto packets = chunk_header->packets.load(std::memory_order_relaxed);
@@ -421,6 +423,18 @@
       chunk_header->packets.store(packets, std::memory_order_release);
     }
 
+    // This flag can only be cleared by the producer while it is still holding
+    // on to the chunk - i.e. while the chunk is still in state
+    // ChunkState::kChunkBeingWritten and hasn't been transitioned to
+    // ChunkState::kChunkComplete. This is ok, because the service is oblivious
+    // to the needs patching flag before the chunk is released as complete.
+    void ClearNeedsPatchingFlag() {
+      ChunkHeader* chunk_header = header();
+      auto packets = chunk_header->packets.load(std::memory_order_relaxed);
+      packets.flags &= ~ChunkHeader::kChunkNeedsPatching;
+      chunk_header->packets.store(packets, std::memory_order_release);
+    }
+
    private:
     friend class SharedMemoryABI;
     Chunk(uint8_t* begin, uint16_t size, uint8_t chunk_idx);
@@ -515,7 +529,9 @@
     return TryAcquireChunk(page_idx, chunk_idx, kChunkBeingRead, nullptr);
   }
 
-  // The caller must have successfully TryAcquireAllChunksForReading().
+  // The caller must have successfully TryAcquireAllChunksForReading() or it
+  // needs to guarantee that the chunk is already in the kChunkBeingWritten
+  // state.
   Chunk GetChunkUnchecked(size_t page_idx,
                           uint32_t page_layout,
                           size_t chunk_idx);
diff --git a/include/perfetto/ext/tracing/core/shared_memory_arbiter.h b/include/perfetto/ext/tracing/core/shared_memory_arbiter.h
index 69849bb..8d371a3 100644
--- a/include/perfetto/ext/tracing/core/shared_memory_arbiter.h
+++ b/include/perfetto/ext/tracing/core/shared_memory_arbiter.h
@@ -113,6 +113,61 @@
   // committed in the shared memory buffer. Should only be called while bound.
   virtual void NotifyFlushComplete(FlushRequestID) = 0;
 
+  // Sets the duration during which commits are batched. Args:
+  // |batch_commits_duration_ms|: The length of the period, during which commits
+  // by all trace writers are accumulated, before being sent to the service.
+  // When the period ends, all accumulated commits are flushed. On the first
+  // commit after the last flush, another delayed flush is scheduled to run in
+  // |batch_commits_duration_ms|. If an immediate flush occurs (via
+  // FlushPendingCommitDataRequests()) during a batching period, any
+  // accumulated commits up to that point will be sent to the service
+  // immediately. And when the batching period ends, the commits that occurred
+  // after the immediate flush will also be sent to the service.
+  //
+  // If the duration has already been set to a non-zero value before this method
+  // is called, and there is already a scheduled flush with the previously-set
+  // duration, the new duration will take effect after the scheduled flush
+  // occurs.
+  //
+  // If |batch_commits_duration_ms| is non-zero, batched data that hasn't been
+  // sent could be lost at the end of a tracing session. To avoid this,
+  // producers should make sure that FlushPendingCommitDataRequests is called
+  // after the last TraceWriter write and before the service has stopped
+  // listening for commits from the tracing session's data sources (i.e.
+  // data sources should stop asynchronously, see
+  // DataSourceDescriptor.will_notify_on_stop=true).
+  virtual void SetBatchCommitsDuration(uint32_t batch_commits_duration_ms) = 0;
+
+  // Called to enable direct producer-side patching of chunks that have not yet
+  // been committed to the service. The return value indicates whether direct
+  // patching was successfully enabled. It will be true if
+  // SharedMemoryArbiter::SetDirectSMBPatchingSupportedByService has been called
+  // and false otherwise.
+  virtual bool EnableDirectSMBPatching() = 0;
+
+  // When the producer and service live in separate processes, this method
+  // should be called if the producer receives an
+  // InitializeConnectionResponse.direct_smb_patching_supported set to true by
+  // the service (see producer_port.proto) .
+  //
+  // In the in-process case, the service will always support direct SMB patching
+  // and this method should always be called.
+  virtual void SetDirectSMBPatchingSupportedByService() = 0;
+
+  // Forces an immediate commit of the completed packets, without waiting for
+  // the next task or for a batching period to end. Should only be called while
+  // bound.
+  virtual void FlushPendingCommitDataRequests(
+      std::function<void()> callback = {}) = 0;
+
+  // Attempts to shut down this arbiter. This function prevents new trace
+  // writers from being created for this this arbiter, but if there are any
+  // existing trace writers, the shutdown cannot proceed and this funtion
+  // returns false. The caller should not delete the arbiter before all of its
+  // associated trace writers have been destroyed and this function returns
+  // true.
+  virtual bool TryShutdown() = 0;
+
   // Create a bound arbiter instance. Args:
   // |SharedMemory|: the shared memory buffer to use.
   // |page_size|: a multiple of 4KB that defines the granularity of tracing
diff --git a/include/perfetto/ext/tracing/core/tracing_service.h b/include/perfetto/ext/tracing/core/tracing_service.h
index 3a72e90..20382a1 100644
--- a/include/perfetto/ext/tracing/core/tracing_service.h
+++ b/include/perfetto/ext/tracing/core/tracing_service.h
@@ -41,6 +41,9 @@
 class SharedMemoryArbiter;
 class TraceWriter;
 
+// Exposed for testing.
+extern const char* kBugreportTracePath;
+
 // TODO: for the moment this assumes that all the calls happen on the same
 // thread/sequence. Not sure this will be the case long term in Chrome.
 
@@ -222,6 +225,20 @@
   using QueryCapabilitiesCallback =
       std::function<void(const TracingServiceCapabilities&)>;
   virtual void QueryCapabilities(QueryCapabilitiesCallback) = 0;
+
+  // If any tracing session with TraceConfig.bugreport_score > 0 is running,
+  // this will pick the highest-score one, stop it and save it into a fixed
+  // path (See kBugreportTracePath).
+  // The callback is invoked when the file has been saved, in case of success,
+  // or whenever an error occurs.
+  // Args:
+  // - success: if true, an eligible trace was found and saved into file.
+  //            If false, either there was no eligible trace running or
+  //            something else failed (See |msg|).
+  // - msg: human readable diagnostic messages to debug failures.
+  using SaveTraceForBugreportCallback =
+      std::function<void(bool /*success*/, const std::string& /*msg*/)>;
+  virtual void SaveTraceForBugreport(SaveTraceForBugreportCallback) = 0;
 };  // class ConsumerEndpoint.
 
 // The public API of the tracing Service business logic.
diff --git a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
index 5469ded..da496b9 100644
--- a/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
+++ b/include/perfetto/ext/tracing/ipc/producer_ipc_client.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "perfetto/base/export.h"
+#include "perfetto/ext/ipc/client.h"
 #include "perfetto/ext/tracing/core/shared_memory.h"
 #include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
 #include "perfetto/ext/tracing/core/tracing_service.h"
@@ -36,6 +37,16 @@
 //   src/tracing/ipc/producer/producer_ipc_client_impl.cc
 class PERFETTO_EXPORT ProducerIPCClient {
  public:
+  enum class ConnectionFlags {
+    // Fails immediately with OnConnect(false) if the service connection cannot
+    // be established.
+    kDefault = 0,
+
+    // Keeps retrying with exponential backoff indefinitely. The caller will
+    // never see an OnConnect(false).
+    kRetryIfUnreachable = 1,
+  };
+
   // Connects to the producer port of the Service listening on the given
   // |service_sock_name|. If the connection is successful, the OnConnect()
   // method will be invoked asynchronously on the passed Producer interface. If
@@ -51,6 +62,8 @@
   // until the client is destroyed.
   //
   // TODO(eseckler): Support adoption failure more gracefully.
+  // TODO(primiano): move all the existing use cases to the Connect(ConnArgs)
+  // below. Also move the functionality of ConnectionFlags into ConnArgs.
   static std::unique_ptr<TracingService::ProducerEndpoint> Connect(
       const char* service_sock_name,
       Producer*,
@@ -61,6 +74,21 @@
       size_t shared_memory_size_hint_bytes = 0,
       size_t shared_memory_page_size_hint_bytes = 0,
       std::unique_ptr<SharedMemory> shm = nullptr,
+      std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr,
+      ConnectionFlags = ConnectionFlags::kDefault);
+
+  // Overload of Connect() to support adopting a connected socket using
+  // ipc::Client::ConnArgs.
+  static std::unique_ptr<TracingService::ProducerEndpoint> Connect(
+      ipc::Client::ConnArgs,
+      Producer*,
+      const std::string& producer_name,
+      base::TaskRunner*,
+      TracingService::ProducerSMBScrapingMode smb_scraping_mode =
+          TracingService::ProducerSMBScrapingMode::kDefault,
+      size_t shared_memory_size_hint_bytes = 0,
+      size_t shared_memory_page_size_hint_bytes = 0,
+      std::unique_ptr<SharedMemory> shm = nullptr,
       std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
 
  protected:
diff --git a/include/perfetto/profiling/deobfuscator.h b/include/perfetto/profiling/deobfuscator.h
index a6f467a..1a285a9 100644
--- a/include/perfetto/profiling/deobfuscator.h
+++ b/include/perfetto/profiling/deobfuscator.h
@@ -17,19 +17,73 @@
 #ifndef INCLUDE_PERFETTO_PROFILING_DEOBFUSCATOR_H_
 #define INCLUDE_PERFETTO_PROFILING_DEOBFUSCATOR_H_
 
+#include <functional>
 #include <map>
 #include <string>
+#include <utility>
+#include <vector>
 
 namespace perfetto {
 namespace profiling {
 
-struct ObfuscatedClass {
-  ObfuscatedClass(std::string d) : deobfuscated_name(std::move(d)) {}
-  ObfuscatedClass(std::string d, std::map<std::string, std::string> f)
-      : deobfuscated_name(std::move(d)), deobfuscated_fields(std::move(f)) {}
+std::string FlattenClasses(
+    const std::map<std::string, std::vector<std::string>>& m);
 
-  std::string deobfuscated_name;
-  std::map<std::string, std::string> deobfuscated_fields;
+class ObfuscatedClass {
+ public:
+  explicit ObfuscatedClass(std::string d) : deobfuscated_name_(std::move(d)) {}
+  ObfuscatedClass(
+      std::string d,
+      std::map<std::string, std::string> f,
+      std::map<std::string, std::map<std::string, std::vector<std::string>>> m)
+      : deobfuscated_name_(std::move(d)),
+        deobfuscated_fields_(std::move(f)),
+        deobfuscated_methods_(std::move(m)) {}
+
+  const std::string& deobfuscated_name() const { return deobfuscated_name_; }
+
+  const std::map<std::string, std::string>& deobfuscated_fields() const {
+    return deobfuscated_fields_;
+  }
+
+  std::map<std::string, std::string> deobfuscated_methods() const {
+    std::map<std::string, std::string> result;
+    for (const auto& p : deobfuscated_methods_) {
+      result.emplace(p.first, FlattenClasses(p.second));
+    }
+    return result;
+  }
+
+  bool redefined_methods() const { return redefined_methods_; }
+
+  bool AddField(std::string obfuscated_name, std::string deobfuscated_name) {
+    auto p = deobfuscated_fields_.emplace(std::move(obfuscated_name),
+                                          deobfuscated_name);
+    return p.second && p.first->second == deobfuscated_name;
+  }
+
+  void AddMethod(std::string obfuscated_name, std::string deobfuscated_name) {
+    std::string cls = deobfuscated_name_;
+    auto dot = deobfuscated_name.rfind('.');
+    if (dot != std::string::npos) {
+      cls = deobfuscated_name.substr(0, dot);
+      deobfuscated_name = deobfuscated_name.substr(dot + 1);
+    }
+    auto& deobfuscated_names_for_cls =
+        deobfuscated_methods_[std::move(obfuscated_name)][std::move(cls)];
+    deobfuscated_names_for_cls.push_back(std::move(deobfuscated_name));
+    if (deobfuscated_names_for_cls.size() > 1 ||
+        deobfuscated_methods_.size() > 1) {
+      redefined_methods_ = true;
+    }
+  }
+
+ private:
+  std::string deobfuscated_name_;
+  std::map<std::string, std::string> deobfuscated_fields_;
+  std::map<std::string, std::map<std::string, std::vector<std::string>>>
+      deobfuscated_methods_;
+  bool redefined_methods_ = false;
 };
 
 class ProguardParser {
@@ -37,6 +91,7 @@
   // A return value of false means this line failed to parse. This leaves the
   // parser in an undefined state and it should no longer be used.
   bool AddLine(std::string line);
+  bool AddLines(std::string contents);
 
   std::map<std::string, ObfuscatedClass> ConsumeMapping() {
     return std::move(mapping_);
@@ -47,6 +102,22 @@
   ObfuscatedClass* current_class_ = nullptr;
 };
 
+struct ProguardMap {
+  std::string package;
+  std::string filename;
+};
+
+void MakeDeobfuscationPackets(
+    const std::string& package_name,
+    const std::map<std::string, profiling::ObfuscatedClass>& mapping,
+    std::function<void(const std::string&)> callback);
+
+std::vector<ProguardMap> GetPerfettoProguardMapPath();
+
+bool ReadProguardMapsToDeobfuscationPackets(
+    const std::vector<ProguardMap>& maps,
+    std::function<void(std::string)> fn);
+
 }  // namespace profiling
 }  // namespace perfetto
 
diff --git a/include/perfetto/profiling/memory/client_ext.h b/include/perfetto/profiling/memory/client_ext.h
deleted file mode 100644
index ca4ce2a..0000000
--- a/include/perfetto/profiling/memory/client_ext.h
+++ /dev/null
@@ -1,66 +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 INCLUDE_PERFETTO_PROFILING_MEMORY_CLIENT_EXT_H_
-#define INCLUDE_PERFETTO_PROFILING_MEMORY_CLIENT_EXT_H_
-
-#include <inttypes.h>
-#include <stdlib.h>
-
-#define HEAPPROFD_HEAP_NAME_SZ 32
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Metadata of a custom heap.
-//
-// NB: This struct is append only. Be very careful that the ABI of this does
-// not change. We want to be able to correctly handle structs from clients
-// that compile against old versions of this header, setting all the newly
-// added fields to zero.
-//
-// TODO(fmayer): Sort out alignment etc. before stabilizing the ABI.
-struct HeapprofdHeapInfo {
-  char heap_name[HEAPPROFD_HEAP_NAME_SZ];
-  // Gets called when heap profiling gets enabled or disabled.
-  void (*callback)(bool /* enabled */);
-};
-
-// Called by libc upon receipt of the profiling signal.
-// DO NOT CALL FROM OTHER CLIENTS!
-bool heapprofd_init_session(void* (*malloc_fn)(size_t), void (*free_fn)(void*));
-
-// Register a heap. Options are given in the HeapprofdHeapInfo struct.
-//
-// On error, returns 0, which can be safely passed to any function, and will
-// turn them into a no-op.
-uint32_t heapprofd_register_heap(const HeapprofdHeapInfo* heap_info, size_t n);
-
-// Reports an allocation on the given heap.
-// Returns whether the allocation was sampled.
-bool heapprofd_report_allocation(uint32_t heap_id, uint64_t id, uint64_t size);
-
-// Report allocation was freed on the given heap.
-// It is allowed to call with an id that was not previously reported as
-// allocated, in which case it does not change the output.
-void heapprofd_report_free(uint32_t heap_id, uint64_t id);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // INCLUDE_PERFETTO_PROFILING_MEMORY_CLIENT_EXT_H_
diff --git a/include/perfetto/profiling/memory/heap_profile.h b/include/perfetto/profiling/memory/heap_profile.h
new file mode 100644
index 0000000..6bd337c
--- /dev/null
+++ b/include/perfetto/profiling/memory/heap_profile.h
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+
+// API to report allocations to heapprofd. This allows users to see the
+// callstacks causing these allocations in heap profiles.
+//
+// In the context of this API, a "heap" is memory associated with an allocator.
+// An example for allocator is the malloc-family of libc functions (malloc /
+// calloc / posix_memalign).
+//
+// A very simple custom allocator would look like this:
+//
+// void* my_malloc(size_t size) {
+//   void* ptr = [code to somehow allocate get size bytes];
+//   return ptr;
+// }
+//
+// void my_free(void* ptr) {
+//   [code to somehow free ptr]
+// }
+//
+// To find out where in a program these two functions get called, we instrument
+// the allocator using this API:
+//
+// static uint32_t g_heap_id =
+//   AHeapProfile_registerHeap(AHeapInfo_create("invalid.example"));
+//
+// void* my_malloc(size_t size) {
+//   void* ptr = [code to somehow allocate get size bytes];
+//   AHeapProfile_reportAllocation(g_heap_id, static_cast<uintptr_t>(ptr),
+//   size); return ptr;
+// }
+//
+// void my_free(void* ptr) {
+//   AHeapProfile_reportFree(g_heap_id, static_cast<uintptr_t>(ptr));
+//   [code to somehow free ptr]
+// }
+//
+// This will allow users to get a flamegraph of the callstacks calling into
+// these functions.
+//
+// See https://perfetto.dev/docs/data-sources/native-heap-profiler for more
+// information on heapprofd in general.
+
+#ifndef INCLUDE_PERFETTO_PROFILING_MEMORY_HEAP_PROFILE_H_
+#define INCLUDE_PERFETTO_PROFILING_MEMORY_HEAP_PROFILE_H_
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#pragma GCC diagnostic push
+
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wnullability-extension"
+#else
+#define _Nullable
+#define _Nonnull
+#endif
+
+// Maximum size of heap name, including NUL-byte.
+#define HEAPPROFD_HEAP_NAME_SZ 64
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct AHeapInfo AHeapInfo;
+typedef struct AHeapProfileEnableCallbackInfo AHeapProfileEnableCallbackInfo;
+typedef struct AHeapProfileDisableCallbackInfo AHeapProfileDisableCallbackInfo;
+
+// Get sampling interval of the profiling session that was started.
+uint64_t AHeapProfileEnableCallbackInfo_getSamplingInterval(
+    const AHeapProfileEnableCallbackInfo* _Nonnull session_info);
+
+// Create new AHeapInfo, a struct describing a heap.
+//
+// Takes name of the heap, up to 64 bytes including null terminator. To
+// guarantee uniqueness, this should include the caller's domain name,
+// e.g. "dev.perfetto.largeobjects".
+//
+// On error, returns NULL.
+// Errors are:
+//  * Empty or too long (larger than 64 bytes including null terminator)
+//    heap_name.
+//  * Too many heaps have been registered in this process already.
+//
+// Must eventually be passed to AHeapProfile_registerHeap.
+AHeapInfo* _Nullable AHeapInfo_create(const char* _Nonnull heap_name);
+
+// Set enabled callback in AHeapInfo.
+//
+// If info is NULL, do nothing.
+//
+// After this AHeapInfo is registered via AHeapProfile_registerHeap,
+// this callback is called when profiling of the heap is requested.
+AHeapInfo* _Nullable AHeapInfo_setEnabledCallback(
+    AHeapInfo* _Nullable info,
+    void (*_Nonnull callback)(
+        void* _Nullable,
+        const AHeapProfileEnableCallbackInfo* _Nonnull session_info),
+    void* _Nullable data);
+
+// Set disabled callback in AHeapInfo.
+//
+// If info is NULL, do nothing.
+//
+// After this AHeapInfo is registered via AHeapProfile_registerHeap,
+// this callback is called when profiling of the heap ends.
+AHeapInfo* _Nullable AHeapInfo_setDisabledCallback(
+    AHeapInfo* _Nullable info,
+    void (*_Nonnull callback)(
+        void* _Nullable,
+        const AHeapProfileDisableCallbackInfo* _Nonnull session_info),
+    void* _Nullable data);
+
+// Register heap described in AHeapInfo.
+//
+// If info is NULL, return a no-op heap_id.
+//
+// The returned heap_id can be used in AHeapProfile_reportAllocation and
+// AHeapProfile_reportFree.
+//
+// Takes ownership of info.
+uint32_t AHeapProfile_registerHeap(AHeapInfo* _Nullable info);
+
+// Called by libc upon receipt of the profiling signal.
+// DO NOT CALL EXCEPT FROM LIBC!
+// TODO(fmayer): Maybe move this out of this header.
+bool AHeapProfile_initSession(void* _Nullable (*_Nonnull malloc_fn)(size_t),
+                              void (*_Nonnull free_fn)(void* _Nullable));
+
+// Reports an allocation of |size| on the given |heap_id|.
+//
+// If a profiling session is active, this function decides whether the reported
+// allocation should be sampled. If the allocation is sampled, it will be
+// associated to the current callstack in the profile.
+//
+// Returns whether the allocation was sampled.
+bool AHeapProfile_reportAllocation(uint32_t heap_id,
+                                   uint64_t alloc_id,
+                                   uint64_t size);
+
+// Reports a sample of |size| on the given |heap_id|.
+//
+// If a profiling session is active, this function associates the sample with
+// the current callstack in the profile.
+//
+// Returns whether the profiling session was active.
+//
+// THIS IS GENERALLY NOT WHAT YOU WANT. THIS IS ONLY NEEDED IF YOU NEED TO
+// DO THE SAMPLING YOURSELF FOR PERFORMANCE REASONS.
+// USE AHeapProfile_reportAllocation TO REPORT AN ALLOCATION AND LET
+// HEAPPROFD DO THE SAMPLING.
+//
+// TODO(fmayer): Make this unavailable to non-Mainline.
+bool AHeapProfile_reportSample(uint32_t heap_id,
+                               uint64_t alloc_id,
+                               uint64_t size);
+
+// Report allocation was freed on the given heap.
+//
+// If |alloc_id| was sampled in a previous call to
+// AHeapProfile_reportAllocation, this allocation is marked as freed in the
+// profile.
+//
+// It is allowed to call with an |alloc_id| that was either not sampled or never
+// passed to AHeapProfile_reportAllocation, in which case the call will not
+// change the output.
+void AHeapProfile_reportFree(uint32_t heap_id, uint64_t alloc_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#pragma GCC diagnostic pop
+
+#endif  // INCLUDE_PERFETTO_PROFILING_MEMORY_HEAP_PROFILE_H_
diff --git a/include/perfetto/profiling/parse_smaps.h b/include/perfetto/profiling/parse_smaps.h
index 47d11c9..e6c260c 100644
--- a/include/perfetto/profiling/parse_smaps.h
+++ b/include/perfetto/profiling/parse_smaps.h
@@ -25,6 +25,7 @@
 
 #include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <inttypes.h>
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index db3814d..20b9d2e 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -33,26 +33,24 @@
 
 namespace trace_to_text {
 
-struct SerializedProfile {
-  uint64_t pid;
-  std::string heap_name;
-  std::string serialized;
+enum class ProfileType {
+  kHeapProfile,
+  kPerfProfile,
 };
 
-bool TraceToPprof(trace_processor::TraceProcessor*,
-                  std::vector<SerializedProfile>* output,
-                  profiling::Symbolizer* symbolizer,
-                  uint64_t pid = 0,
-                  const std::vector<uint64_t>& timestamps = {});
+struct SerializedProfile {
+  ProfileType profile_type;
+  uint64_t pid;
+  std::string serialized;
+  // non-empty if profile_type == kHeapProfile
+  std::string heap_name;
+};
 
-bool TraceToPprof(std::istream* input,
-                  std::vector<SerializedProfile>* output,
-                  profiling::Symbolizer* symbolizer,
-                  uint64_t pid = 0,
-                  const std::vector<uint64_t>& timestamps = {});
+enum class ConversionMode { kHeapProfile, kPerfProfile };
 
-bool TraceToPprof(std::istream* input,
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
                   std::vector<SerializedProfile>* output,
+                  ConversionMode mode = ConversionMode::kHeapProfile,
                   uint64_t pid = 0,
                   const std::vector<uint64_t>& timestamps = {});
 
diff --git a/include/perfetto/protozero/BUILD.gn b/include/perfetto/protozero/BUILD.gn
index ee0f77a..93fdce4 100644
--- a/include/perfetto/protozero/BUILD.gn
+++ b/include/perfetto/protozero/BUILD.gn
@@ -20,10 +20,12 @@
     "cpp_message_obj.h",
     "field.h",
     "message.h",
+    "message_arena.h",
     "message_handle.h",
     "packed_repeated_fields.h",
     "proto_decoder.h",
     "proto_utils.h",
+    "root_message.h",
     "scattered_heap_buffer.h",
     "scattered_stream_null_delegate.h",
     "scattered_stream_writer.h",
diff --git a/include/perfetto/protozero/message.h b/include/perfetto/protozero/message.h
index b53dcda..a47db7e 100644
--- a/include/perfetto/protozero/message.h
+++ b/include/perfetto/protozero/message.h
@@ -38,30 +38,28 @@
 
 namespace protozero {
 
+class MessageArena;
 class MessageHandleBase;
 
 // Base class extended by the proto C++ stubs generated by the ProtoZero
 // compiler. This class provides the minimal runtime required to support
 // append-only operations and is designed for performance. None of the methods
-// require any dynamic memory allocation.
+// require any dynamic memory allocation, unless more than 16 nested messages
+// are created via BeginNestedMessage() calls.
 class PERFETTO_EXPORT Message {
  public:
   friend class MessageHandleBase;
 
-  // Adjust the |nested_messages_arena_| size when changing this, or the
-  // static_assert in the .cc file will bark.
-  static constexpr uint32_t kMaxNestingDepth = 10;
-
-  // Ctor and Dtor of Message are never called, with the exeception
-  // of root (non-nested) messages. Nested messages are allocated via placement
-  // new in the |nested_messages_arena_| and implictly destroyed when the arena
-  // of the root message goes away. This is fine as long as all the fields are
-  // PODs, which is checked by the static_assert in the ctor (see the Reset()
-  // method in the .cc file).
+  // The ctor is deliberately a no-op to avoid forwarding args from all
+  // subclasses. The real initialization is performed by Reset().
+  // Nested messages are allocated via placement new by MessageArena and
+  // implictly destroyed when the RootMessage's arena goes away. This is
+  // fine as long as all the fields are PODs, which is checked by the
+  // static_assert()s in the Reset() method.
   Message() = default;
 
   // Clears up the state, allowing the message to be reused as a fresh one.
-  void Reset(ScatteredStreamWriter*);
+  void Reset(ScatteredStreamWriter*, MessageArena*);
 
   // Commits all the changes to the buffer (backfills the size field of this and
   // all nested messages) and seals the message. Returns the size of the message
@@ -156,10 +154,9 @@
                               ContiguousMemoryRange* ranges,
                               size_t num_ranges);
 
-  // Begins a nested message, using the static storage provided by the parent
-  // class (see comment in |nested_messages_arena_|). The nested message ends
-  // either when Finalize() is called or when any other Append* method is called
-  // in the parent class.
+  // Begins a nested message. The returned object is owned by the MessageArena
+  // of the root message. The nested message ends either when Finalize() is
+  // called or when any other Append* method is called in the parent class.
   // The template argument T is supposed to be a stub class auto generated from
   // a .proto, hence a subclass of Message.
   template <class T>
@@ -170,9 +167,7 @@
                   "T must be a subclass of Message");
     static_assert(sizeof(T) == sizeof(Message),
                   "Message subclasses cannot introduce extra state.");
-    T* message = reinterpret_cast<T*>(nested_messages_arena_);
-    BeginNestedMessageInternal(field_id, message);
-    return message;
+    return static_cast<T*>(BeginNestedMessageInternal(field_id));
   }
 
   ScatteredStreamWriter* stream_writer_for_testing() { return stream_writer_; }
@@ -191,7 +186,7 @@
   Message(const Message&) = delete;
   Message& operator=(const Message&) = delete;
 
-  void BeginNestedMessageInternal(uint32_t field_id, Message*);
+  Message* BeginNestedMessageInternal(uint32_t field_id);
 
   // Called by Finalize and Append* methods.
   void EndNestedMessage();
@@ -210,6 +205,22 @@
   // The stream writer interface used for the serialization.
   ScatteredStreamWriter* stream_writer_;
 
+  // The storage used to allocate nested Message objects.
+  // This is owned by RootMessage<T>.
+  MessageArena* arena_;
+
+  // Pointer to the last child message created through BeginNestedMessage(), if
+  // any, nullptr otherwise. There is no need to keep track of more than one
+  // message per nesting level as the proto-zero API contract mandates that
+  // nested fields can be filled only in a stacked fashion. In other words,
+  // nested messages are finalized and sealed when any other field is set in the
+  // parent message (or the parent message itself is finalized) and cannot be
+  // accessed anymore afterwards.
+  Message* nested_message_;
+
+  // [optional] Pointer to a non-aligned pre-reserved var-int slot of
+  // kMessageLengthFieldSize bytes. When set, the Finalize() method will write
+  // the size of proto-encoded message in the pointed memory region.
   uint8_t* size_field_;
 
   // Keeps track of the size of the current message.
@@ -222,10 +233,6 @@
   // attempts of writing to a message which has been Finalize()-d.
   bool finalized_;
 
-  // Used to detect attemps to create messages with a nesting level >
-  // kMaxNestingDepth. |nesting_depth_| == 0 for root (non-nested) messages.
-  uint8_t nesting_depth_;
-
 #if PERFETTO_DCHECK_IS_ON()
   // Current generation of message. Incremented on Reset.
   // Used to detect stale handles.
@@ -233,28 +240,6 @@
 
   MessageHandleBase* handle_;
 #endif
-
-  // Pointer to the last child message created through BeginNestedMessage(), if
-  // any, nullptr otherwise. There is no need to keep track of more than one
-  // message per nesting level as the proto-zero API contract mandates that
-  // nested fields can be filled only in a stacked fashion. In other words,
-  // nested messages are finalized and sealed when any other field is set in the
-  // parent message (or the parent message itself is finalized) and cannot be
-  // accessed anymore afterwards.
-  // TODO(primiano): optimization: I think that nested_message_, when non-null.
-  // will always be @ (this) + offsetof(nested_messages_arena_).
-  Message* nested_message_;
-
-  // The root message owns the storage for all its nested messages, up to a max
-  // of kMaxNestingDepth levels (see the .cc file). Note that the boundaries of
-  // the arena are meaningful only for the root message.
-  // Unfortunately we cannot put the sizeof() math here because we cannot sizeof
-  // the current class in a header. However the .cc file has a static_assert
-  // that guarantees that (see the Reset() method in the .cc file).
-  alignas(sizeof(void*)) uint8_t nested_messages_arena_[512];
-
-  // DO NOT add any fields below |nested_messages_arena_|. The memory layout of
-  // nested messages would overflow the storage allocated by the root message.
 };
 
 }  // namespace protozero
diff --git a/include/perfetto/protozero/message_arena.h b/include/perfetto/protozero/message_arena.h
new file mode 100644
index 0000000..4905dae
--- /dev/null
+++ b/include/perfetto/protozero/message_arena.h
@@ -0,0 +1,95 @@
+/*
+ * 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 INCLUDE_PERFETTO_PROTOZERO_MESSAGE_ARENA_H_
+#define INCLUDE_PERFETTO_PROTOZERO_MESSAGE_ARENA_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <type_traits>
+
+#include "perfetto/base/export.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/protozero/message.h"
+
+namespace protozero {
+
+class Message;
+
+// Object allocator for fixed-sized protozero::Message objects.
+// It's a simple bump-pointer allocator which leverages the stack-alike
+// usage pattern of protozero nested messages. It avoids hitting the system
+// allocator in most cases, by reusing the same block, and falls back on
+// allocating new blocks only when using deeply nested messages (which are
+// extremely rare).
+// This is used by RootMessage<T> to handle the storage for root-level messages.
+class PERFETTO_EXPORT MessageArena {
+ public:
+  MessageArena();
+  ~MessageArena();
+
+  // Strictly no copies or moves as this is used to hand out pointers.
+  MessageArena(const MessageArena&) = delete;
+  MessageArena& operator=(const MessageArena&) = delete;
+  MessageArena(MessageArena&&) = delete;
+  MessageArena& operator=(MessageArena&&) = delete;
+
+  // Allocates a new Message object.
+  Message* NewMessage();
+
+  // Deletes the last message allocated. The |msg| argument is used only for
+  // DCHECKs, it MUST be the pointer obtained by the last NewMessage() call.
+  void DeleteLastMessage(Message* msg) {
+    PERFETTO_DCHECK(!blocks_.empty() && blocks_.back().entries > 0);
+    PERFETTO_DCHECK(&blocks_.back().storage[blocks_.back().entries - 1] ==
+                    static_cast<void*>(msg));
+    DeleteLastMessageInternal();
+  }
+
+  // Resets the state of the arena, clearing up all but one block. This is used
+  // to avoid leaking outstanding unfinished sub-messages while recycling the
+  // RootMessage object (this is extremely rare due to the RAII scoped handles
+  // but could happen if some client does some overly clever std::move() trick).
+  void Reset() {
+    PERFETTO_DCHECK(!blocks_.empty());
+    blocks_.resize(1);
+    auto& block = blocks_.back();
+    block.entries = 0;
+    PERFETTO_ASAN_POISON(block.storage, sizeof(block.storage));
+  }
+
+ private:
+  void DeleteLastMessageInternal();
+
+  struct Block {
+    static constexpr size_t kCapacity = 16;
+
+    Block() { PERFETTO_ASAN_POISON(storage, sizeof(storage)); }
+
+    std::aligned_storage<sizeof(Message), alignof(Message)>::type
+        storage[kCapacity];
+    uint32_t entries = 0;  // # Message entries used (<= kCapacity).
+  };
+
+  // blocks are used to hand out pointers and must not be moved. Hence why
+  // std::list rather than std::vector.
+  std::list<Block> blocks_;
+};
+
+}  // namespace protozero
+
+#endif  // INCLUDE_PERFETTO_PROTOZERO_MESSAGE_ARENA_H_
diff --git a/include/perfetto/protozero/proto_utils.h b/include/perfetto/protozero/proto_utils.h
index d8f0592..df7386d 100644
--- a/include/perfetto/protozero/proto_utils.h
+++ b/include/perfetto/protozero/proto_utils.h
@@ -162,9 +162,10 @@
 template <typename T>
 inline typename std::make_signed<T>::type ZigZagDecode(T value) {
   using UnsignedType = typename std::make_unsigned<T>::type;
+  using SignedType = typename std::make_signed<T>::type;
   auto u_value = static_cast<UnsignedType>(value);
-  return static_cast<typename std::make_signed<T>::type>(
-      ((u_value >> 1) ^ -(u_value & 1)));
+  auto mask = static_cast<UnsignedType>(-static_cast<SignedType>(u_value & 1));
+  return static_cast<SignedType>((u_value >> 1) ^ mask);
 }
 
 template <typename T>
diff --git a/include/perfetto/protozero/root_message.h b/include/perfetto/protozero/root_message.h
new file mode 100644
index 0000000..40e2328
--- /dev/null
+++ b/include/perfetto/protozero/root_message.h
@@ -0,0 +1,53 @@
+/*
+ * 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 INCLUDE_PERFETTO_PROTOZERO_ROOT_MESSAGE_H_
+#define INCLUDE_PERFETTO_PROTOZERO_ROOT_MESSAGE_H_
+
+#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/message_arena.h"
+
+namespace protozero {
+
+// Helper class to hand out messages using the default MessageArena.
+// Usage:
+// RootMessage<perfetto::protos::zero::MyMessage> msg;
+// msg.Reset(stream_writer);
+// msg.set_foo(...);
+// auto* nested = msg.set_nested();
+template <typename T = Message>
+class RootMessage : public T {
+ public:
+  RootMessage() { T::Reset(nullptr, &root_arena_); }
+
+  // Disallow copy and move.
+  RootMessage(const RootMessage&) = delete;
+  RootMessage& operator=(const RootMessage&) = delete;
+  RootMessage(RootMessage&&) = delete;
+  RootMessage& operator=(RootMessage&&) = delete;
+
+  void Reset(ScatteredStreamWriter* writer) {
+    root_arena_.Reset();
+    Message::Reset(writer, &root_arena_);
+  }
+
+ private:
+  MessageArena root_arena_;
+};
+
+}  // namespace protozero
+
+#endif  // INCLUDE_PERFETTO_PROTOZERO_ROOT_MESSAGE_H_
diff --git a/include/perfetto/protozero/scattered_heap_buffer.h b/include/perfetto/protozero/scattered_heap_buffer.h
index bc6a0d2..d62612e 100644
--- a/include/perfetto/protozero/scattered_heap_buffer.h
+++ b/include/perfetto/protozero/scattered_heap_buffer.h
@@ -23,6 +23,7 @@
 
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
 
 namespace protozero {
@@ -71,6 +72,10 @@
   // protozero::ScatteredStreamWriter::Delegate implementation.
   protozero::ContiguousMemoryRange GetNewBuffer() override;
 
+  // Return the slices backing this buffer, adjusted for the number of bytes the
+  // writer has written.
+  const std::vector<Slice>& GetSlices();
+
   // Stitch all the slices into a single contiguous buffer.
   std::vector<uint8_t> StitchSlices();
 
@@ -78,6 +83,8 @@
   // outlive it.
   std::vector<protozero::ContiguousMemoryRange> GetRanges();
 
+  // Note that size of the last slice isn't updated to reflect the number of
+  // bytes written by the trace writer.
   const std::vector<Slice>& slices() const { return slices_; }
 
   void set_writer(protozero::ScatteredStreamWriter* writer) {
@@ -155,6 +162,11 @@
     return shb_.GetRanges();
   }
 
+  const std::vector<ScatteredHeapBuffer::Slice>& GetSlices() {
+    msg_.Finalize();
+    return shb_.GetSlices();
+  }
+
   void Reset() {
     shb_.Reset();
     writer_.Reset(protozero::ContiguousMemoryRange{});
@@ -165,7 +177,7 @@
  private:
   ScatteredHeapBuffer shb_;
   ScatteredStreamWriter writer_;
-  T msg_;
+  RootMessage<T> msg_;
 };
 
 }  // namespace protozero
diff --git a/include/perfetto/protozero/static_buffer.h b/include/perfetto/protozero/static_buffer.h
index 6f5924f..3d7ec3d 100644
--- a/include/perfetto/protozero/static_buffer.h
+++ b/include/perfetto/protozero/static_buffer.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "perfetto/base/export.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
 
 namespace protozero {
@@ -79,7 +80,7 @@
  private:
   StaticBufferDelegate delegate_;
   ScatteredStreamWriter writer_;
-  T msg_;
+  RootMessage<T> msg_;
 };
 
 // Helper function to create stack-based protozero messages in one line.
diff --git a/include/perfetto/trace_processor/BUILD.gn b/include/perfetto/trace_processor/BUILD.gn
index bdaf789..ed1f3a9 100644
--- a/include/perfetto/trace_processor/BUILD.gn
+++ b/include/perfetto/trace_processor/BUILD.gn
@@ -34,4 +34,5 @@
     "basic_types.h",
     "status.h",
   ]
+  public_deps = [ "../base" ]
 }
diff --git a/include/perfetto/trace_processor/status.h b/include/perfetto/trace_processor/status.h
index ee7271a..6ee417e 100644
--- a/include/perfetto/trace_processor/status.h
+++ b/include/perfetto/trace_processor/status.h
@@ -17,67 +17,20 @@
 #ifndef INCLUDE_PERFETTO_TRACE_PROCESSOR_STATUS_H_
 #define INCLUDE_PERFETTO_TRACE_PROCESSOR_STATUS_H_
 
-#include <stdarg.h>
-#include <string>
+#include "perfetto/base/status.h"
 
-#include "perfetto/base/export.h"
+// Once upon a time Status used to live in perfetto::trace_processor. At some
+// point it has been moved up to base. This forwarding header stayed here
+// because of out-of-repo users.
 
 namespace perfetto {
 namespace trace_processor {
-
-// Status and related methods are inside util for consistency with embedders of
-// trace processor.
 namespace util {
 
-// Represents either the success or the failure message of a function.
-// 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).
-class PERFETTO_EXPORT Status {
- public:
-  Status() : ok_(true) {}
-  explicit Status(std::string error) : ok_(false), message_(std::move(error)) {}
+using Status = ::perfetto::base::Status;
 
-  // Copy operations.
-  Status(const Status&) = default;
-  Status& operator=(const Status&) = default;
-
-  // Move operations. The moved-from state is valid but unspecified.
-  Status(Status&&) noexcept = default;
-  Status& operator=(Status&&) = default;
-
-  bool ok() const { return ok_; }
-
-  // Only valid to call when this message has an Err status (i.e. ok() returned
-  // false or operator bool() returned true).
-  const std::string& message() const { return message_; }
-
-  // Only valid to call when this message has an Err status (i.e. ok() returned
-  // false or operator bool() returned true).
-  const char* c_message() const { return message_.c_str(); }
-
- private:
-  bool ok_ = false;
-  std::string message_;
-};
-
-// Returns a status object which represents the Ok status.
-inline Status OkStatus() {
-  return Status();
-}
-
-// Returns a status object which represents an error with the given message
-// formatted using printf.
-__attribute__((__format__(__printf__, 1, 2))) inline Status ErrStatus(
-    const char* format,
-    ...) {
-  va_list ap;
-  va_start(ap, format);
-
-  char buffer[1024];
-  vsnprintf(buffer, sizeof(buffer), format, ap);
-  return Status(std::string(buffer));
-}
+constexpr auto OkStatus = ::perfetto::base::OkStatus;
+constexpr auto ErrStatus = ::perfetto::base::ErrStatus;
 
 }  // namespace util
 }  // namespace trace_processor
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index 0cc7dbf..c949695 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -65,6 +65,19 @@
       const std::vector<std::string>& metric_names,
       std::vector<uint8_t>* metrics_proto) = 0;
 
+  enum MetricResultFormat {
+    kProtoText = 0,
+    kJson = 1,
+  };
+
+  // Computes metrics as the ComputeMetric function above, but instead of
+  // producing proto encoded bytes, the output argument |metrics_string| is
+  // filled with the metric formatted in the requested |format|.
+  virtual util::Status ComputeMetricText(
+      const std::vector<std::string>& metric_names,
+      MetricResultFormat format,
+      std::string* metrics_string) = 0;
+
   // Interrupts the current query. Typically used by Ctrl-C handler.
   virtual void InterruptQuery() = 0;
 
@@ -92,6 +105,12 @@
   // read.
   virtual util::Status DisableAndReadMetatrace(
       std::vector<uint8_t>* trace_proto) = 0;
+
+  // Gets all the currently loaded proto descriptors used in metric computation.
+  // This includes all compiled-in binary descriptors, and all proto descriptors
+  // loaded by trace processor shell at runtime. The message is encoded as
+  // DescriptorSet, defined in perfetto/trace_processor/trace_processor.proto.
+  virtual std::vector<uint8_t> GetMetricDescriptors() = 0;
 };
 
 // When set, logs SQLite actions on the console.
diff --git a/include/perfetto/tracing.h b/include/perfetto/tracing.h
index fca7c70..c542741 100644
--- a/include/perfetto/tracing.h
+++ b/include/perfetto/tracing.h
@@ -24,15 +24,18 @@
 // concern (e.g. chromium), which migh prefer sticking to strict IWYU.
 
 #include "perfetto/tracing/buffer_exhausted_policy.h"
+#include "perfetto/tracing/console_interceptor.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "perfetto/tracing/data_source.h"
+#include "perfetto/tracing/interceptor.h"
 #include "perfetto/tracing/platform.h"
 #include "perfetto/tracing/tracing.h"
 #include "perfetto/tracing/tracing_backend.h"
 #include "perfetto/tracing/track_event.h"
 #include "perfetto/tracing/track_event_interned_data_index.h"
 #include "perfetto/tracing/track_event_legacy.h"
+#include "perfetto/tracing/track_event_state_tracker.h"
 
 #endif  // INCLUDE_PERFETTO_TRACING_H_
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index b81e9f7..01f0f71 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -28,12 +28,15 @@
 
   sources = [
     "buffer_exhausted_policy.h",
+    "console_interceptor.h",
     "data_source.h",
     "debug_annotation.h",
     "event_context.h",
+    "interceptor.h",
     "internal/basic_types.h",
     "internal/data_source_internal.h",
     "internal/in_process_tracing_backend.h",
+    "internal/interceptor_trace_writer.h",
     "internal/system_tracing_backend.h",
     "internal/tracing_muxer.h",
     "internal/tracing_tls.h",
@@ -50,5 +53,6 @@
     "track_event_category_registry.h",
     "track_event_interned_data_index.h",
     "track_event_legacy.h",
+    "track_event_state_tracker.h",
   ]
 }
diff --git a/include/perfetto/tracing/console_interceptor.h b/include/perfetto/tracing/console_interceptor.h
new file mode 100644
index 0000000..e62b90d
--- /dev/null
+++ b/include/perfetto/tracing/console_interceptor.h
@@ -0,0 +1,123 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACING_CONSOLE_INTERCEPTOR_H_
+#define INCLUDE_PERFETTO_TRACING_CONSOLE_INTERCEPTOR_H_
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/tracing/interceptor.h"
+#include "perfetto/tracing/track_event_state_tracker.h"
+
+#include <stdarg.h>
+
+#include <functional>
+#include <map>
+#include <vector>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define PERFETTO_PRINTF_ATTR \
+  __attribute__((format(printf, /*format_index=*/2, /*first_to_check=*/3)))
+#else
+#define PERFETTO_PRINTF_ATTR
+#endif
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && !defined(STDOUT_FILENO)
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+#endif
+
+namespace perfetto {
+namespace protos {
+namespace pbzero {
+class DebugAnnotation_NestedValue_Decoder;
+class TracePacket_Decoder;
+class TrackEvent_Decoder;
+}  // namespace pbzero
+}  // namespace protos
+
+struct ConsoleColor;
+
+class ConsoleInterceptor : public Interceptor<ConsoleInterceptor> {
+ public:
+  ~ConsoleInterceptor() override;
+
+  static void Register();
+  static void OnTracePacket(InterceptorContext context);
+
+  static void SetOutputFdForTesting(int fd);
+
+  void OnSetup(const SetupArgs&) override;
+  void OnStart(const StartArgs&) override;
+  void OnStop(const StopArgs&) override;
+
+  struct ThreadLocalState : public InterceptorBase::ThreadLocalState {
+    ThreadLocalState(ThreadLocalStateArgs&);
+    ~ThreadLocalState() override;
+
+    // Destination file. Assumed to stay valid until the program ends (i.e., is
+    // stderr or stdout).
+    int fd{};
+    bool use_colors{};
+
+    // Messages up to this length are buffered and written atomically. If a
+    // message is longer, it will be printed with multiple writes.
+    std::array<char, 1024> message_buffer{};
+    size_t buffer_pos{};
+
+    // We only support a single trace writer sequence per thread, so the
+    // sequence state is stored in TLS.
+    TrackEventStateTracker::SequenceState sequence_state;
+    uint64_t start_time_ns{};
+  };
+
+ private:
+  class Delegate;
+
+  // Appends a formatted message to |message_buffer_| or directly to the output
+  // file if the buffer is full.
+  static void Printf(InterceptorContext& context,
+                     const char* format,
+                     ...) PERFETTO_PRINTF_ATTR;
+  static void Flush(InterceptorContext& context);
+  static void SetColor(InterceptorContext& context, const ConsoleColor&);
+  static void SetColor(InterceptorContext& context, const char*);
+
+  static void PrintAnnotations(InterceptorContext&,
+                               const protos::pbzero::TrackEvent_Decoder&,
+                               const ConsoleColor& slice_color,
+                               const ConsoleColor& highlight_color);
+  static void PrintNestedValue(
+      InterceptorContext&,
+      const perfetto::protos::pbzero::DebugAnnotation_NestedValue_Decoder&
+          value);
+
+  int fd_ = STDOUT_FILENO;
+  bool use_colors_ = true;
+
+  TrackEventStateTracker::SessionState session_state_;
+  uint64_t start_time_ns_{};
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_CONSOLE_INTERCEPTOR_H_
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index 56a8cf1..05d7903 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -173,7 +173,12 @@
         ::protozero::MessageHandle<::perfetto::protos::pbzero::TracePacket>;
 
     TraceContext(TraceContext&&) noexcept = default;
-    ~TraceContext() = default;
+    ~TraceContext() {
+      // If the data source is being intercepted, flush the trace writer after
+      // each trace point to make sure the interceptor sees the data right away.
+      if (PERFETTO_UNLIKELY(tls_inst_->is_intercepted))
+        Flush();
+    }
 
     TracePacketHandle NewTracePacket() {
       return tls_inst_->trace_writer->NewTracePacket();
@@ -359,11 +364,16 @@
             Traits::GetActiveInstances()->load(std::memory_order_acquire);
         instance_state = static_state_.TryGetCached(instances, i);
         if (!instance_state || !instance_state->trace_lambda_enabled)
-          return;
+          continue;
         tls_inst.backend_id = instance_state->backend_id;
+        tls_inst.backend_connection_id = instance_state->backend_connection_id;
         tls_inst.buffer_id = instance_state->buffer_id;
+        tls_inst.data_source_instance_id =
+            instance_state->data_source_instance_id;
+        tls_inst.is_intercepted = instance_state->interceptor_id != 0;
         tls_inst.trace_writer = tracing_impl->CreateTraceWriter(
-            instance_state, DataSourceType::kBufferExhaustedPolicy);
+            &static_state_, i, instance_state,
+            DataSourceType::kBufferExhaustedPolicy);
         CreateIncrementalState(
             &tls_inst,
             static_cast<typename DataSourceTraits::IncrementalStateType*>(
@@ -386,14 +396,19 @@
   // Can return false to signal failure if attemping to register more than
   // kMaxDataSources (32) data sources types or if tracing hasn't been
   // initialized.
-  static bool Register(const DataSourceDescriptor& descriptor) {
+  // The optional |constructor_args| will be passed to the data source when it
+  // is constructed.
+  template <class... Args>
+  static bool Register(const DataSourceDescriptor& descriptor,
+                       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)static_state_;
     (void)tls_state_;
 
-    auto factory = [] {
-      return std::unique_ptr<DataSourceBase>(new DataSourceType());
+    auto factory = [constructor_args...]() {
+      return std::unique_ptr<DataSourceBase>(
+          new DataSourceType(constructor_args...));
     };
     auto* tracing_impl = internal::TracingMuxer::Get();
     if (!tracing_impl)
diff --git a/include/perfetto/tracing/event_context.h b/include/perfetto/tracing/event_context.h
index fe9c4bb..9e9523f 100644
--- a/include/perfetto/tracing/event_context.h
+++ b/include/perfetto/tracing/event_context.h
@@ -41,12 +41,28 @@
 
   // For Chromium during the transition phase to the client library.
   // TODO(eseckler): Remove once Chromium has switched to client lib entirely.
-  explicit EventContext(protos::pbzero::TrackEvent* event)
-      : event_(event), incremental_state_(nullptr) {}
+  explicit EventContext(
+      protos::pbzero::TrackEvent* event,
+      internal::TrackEventIncrementalState* incremental_state = nullptr)
+      : event_(event), incremental_state_(incremental_state) {}
 
   ~EventContext();
 
-  protos::pbzero::TrackEvent* event() const { return event_; }
+  // Get a TrackEvent message to write typed arguments to.
+  //
+  // event() is a template method to allow callers to specify a subclass of
+  // TrackEvent instead. Those subclasses correspond to TrackEvent message with
+  // application-specific extensions. More information in
+  // design-docs/extensions.md.
+  template <typename EventType = protos::pbzero::TrackEvent>
+  EventType* event() const {
+    // As the method does downcasting, we check that a target subclass does
+    // not add new fields.
+    static_assert(
+        sizeof(EventType) == sizeof(protos::pbzero::TrackEvent),
+        "Event type must be binary-compatible with protos::pbzero::TrackEvent");
+    return static_cast<EventType*>(event_);
+  }
 
  private:
   template <typename, size_t, typename, typename>
diff --git a/include/perfetto/tracing/interceptor.h b/include/perfetto/tracing/interceptor.h
new file mode 100644
index 0000000..60997b0
--- /dev/null
+++ b/include/perfetto/tracing/interceptor.h
@@ -0,0 +1,346 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_
+#define INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_
+
+// An interceptor is used to redirect trace packets written by a data source
+// into a custom backend instead of the normal Perfetto tracing service. For
+// example, the console interceptor prints all trace packets to the console as
+// they are generated. Another potential use is exporting trace data to another
+// tracing service such as Android ATrace or Windows ETW.
+//
+// An interceptor is defined by subclassing the perfetto::Interceptor template:
+//
+// class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+//  public:
+//   ~MyInterceptor() override = default;
+//
+//   // This function is called for each intercepted trace packet. |context|
+//   // contains information about the trace packet as well as other state
+//   // tracked by the interceptor (e.g., see ThreadLocalState).
+//   //
+//   // Intercepted trace data is provided in the form of serialized protobuf
+//   // bytes, accessed through the |context.packet_data| field.
+//   //
+//   // Warning: this function can be called on any thread at any time. See
+//   // below for how to safely access shared interceptor data from here.
+//   static void OnTracePacket(InterceptorContext context) {
+//     perfetto::protos::pbzero::TracePacket::Decoder packet(
+//         context.packet_data.data, context.packet_data.size);
+//     // ... Write |packet| to the desired destination ...
+//   }
+// };
+//
+// An interceptor should be registered before any tracing sessions are started.
+// Note that the interceptor also needs to be activated through the trace config
+// as shown below.
+//
+//   perfetto::InterceptorDescriptor desc;
+//   desc.set_name("my_interceptor");
+//   MyInterceptor::Register(desc);
+//
+// Finally, an interceptor is enabled through the trace config like this:
+//
+//   perfetto::TraceConfig cfg;
+//   auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+//   ds_cfg->set_name("data_source_to_intercept");   // e.g. "track_event"
+//   ds_cfg->mutable_interceptor_config()->set_name("my_interceptor");
+//
+// Once an interceptor is enabled, all data from the affected data sources is
+// sent to the interceptor instead of the main tracing buffer.
+//
+// Interceptor state
+// =================
+//
+// Besides the serialized trace packet data, the |OnTracePacket| interceptor
+// function can access three other types of state:
+//
+// 1. Global state: this is no different from a normal static function, but care
+//    must be taken because |OnTracePacket| can be called concurrently on any
+//    thread at any time.
+//
+// 2. Per-data source instance state: since the interceptor class is
+//    automatically instantiated for each intercepted data source, its fields
+//    can be used to store per-instance data such as the trace config. This data
+//    can be maintained through the OnSetup/OnStart/OnStop callbacks:
+//
+//    class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+//     public:
+//      void OnSetup(const SetupArgs& args) override {
+//        enable_foo_ = args.config.interceptor_config().enable_foo();
+//      }
+//
+//      bool enable_foo_{};
+//    };
+//
+//    In the interceptor function this data must be accessed through a scoped
+//    lock for safety:
+//
+//    class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+//      ...
+//      static void OnTracePacket(InterceptorContext context) {
+//        auto my_interceptor = context.GetInterceptorLocked();
+//        if (my_interceptor) {
+//           // Access fields of MyInterceptor here.
+//           if (my_interceptor->enable_foo_) { ... }
+//        }
+//        ...
+//      }
+//    };
+//
+//    Since accessing this data involves holding a lock, it should be done
+//    sparingly.
+//
+// 3. Per-thread/TraceWriter state: many data sources use interning to avoid
+//    repeating common data in the trace. Since the interning dictionaries are
+//    typically kept individually for each TraceWriter sequence (i.e., per
+//    thread), an interceptor can declare a data structure with lifetime
+//    matching the TraceWriter:
+//
+//    class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+//     public:
+//      struct ThreadLocalState
+//          : public perfetto::InterceptorBase::ThreadLocalState {
+//        ThreadLocalState(ThreadLocalStateArgs&) override = default;
+//        ~ThreadLocalState() override = default;
+//
+//        std::map<size_t, std::string> event_names;
+//      };
+//    };
+//
+//    This per-thread state can then be accessed and maintained in
+//    |OnTracePacket| like this:
+//
+//    class MyInterceptor : public perfetto::Interceptor<MyInterceptor> {
+//      ...
+//      static void OnTracePacket(InterceptorContext context) {
+//        // Updating interned data.
+//        auto& tls = context.GetThreadLocalState();
+//        if (parsed_packet.sequence_flags() & perfetto::protos::pbzero::
+//                TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+//          tls.event_names.clear();
+//        }
+//        for (const auto& entry : parsed_packet.interned_data().event_names())
+//          tls.event_names[entry.iid()] = entry.name();
+//
+//        // Looking up interned data.
+//        if (parsed_packet.has_track_event()) {
+//          size_t name_iid = parsed_packet.track_event().name_iid();
+//          const std::string& event_name = tls.event_names[name_iid];
+//        }
+//        ...
+//      }
+//    };
+//
+
+#include <functional>
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/tracing/core/forward_decls.h"
+#include "perfetto/tracing/internal/basic_types.h"
+#include "perfetto/tracing/internal/data_source_internal.h"
+#include "perfetto/tracing/locked_handle.h"
+
+namespace {
+class MockTracingMuxer;
+}
+
+namespace perfetto {
+namespace protos {
+namespace gen {
+class DataSourceConfig;
+class InterceptorDescriptor;
+}  // namespace gen
+}  // namespace protos
+
+using protos::gen::InterceptorDescriptor;
+
+namespace internal {
+class InterceptorTraceWriter;
+class TracingMuxer;
+class TracingMuxerImpl;
+}  // namespace internal
+
+// A virtual base class for interceptors. Users should derive from the templated
+// subclass below instead of this one.
+class PERFETTO_EXPORT InterceptorBase {
+ public:
+  virtual ~InterceptorBase();
+
+  // A virtual base class for thread-local state needed by the interceptor.
+  // 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 {
+   public:
+    virtual ~ThreadLocalState();
+  };
+
+  struct SetupArgs {
+    const DataSourceConfig& config;
+  };
+  struct StartArgs {};
+  struct StopArgs {};
+
+  // Called when an intercepted data source is set up. Both the interceptor's
+  // and the data source's configuration is available in
+  // |SetupArgs|. Called on an internal Perfetto service thread, but not
+  // concurrently.
+  virtual void OnSetup(const SetupArgs&) {}
+
+  // Called when an intercepted data source starts. Called on an internal
+  // Perfetto service thread, but not concurrently.
+  virtual void OnStart(const StartArgs&) {}
+
+  // Called when an intercepted data source stops. Called on an internal
+  // Perfetto service thread, but not concurrently.
+  virtual void OnStop(const StopArgs&) {}
+
+ private:
+  friend class internal::InterceptorTraceWriter;
+  friend class internal::TracingMuxer;
+  friend class internal::TracingMuxerImpl;
+  friend MockTracingMuxer;
+  template <class T>
+  friend class Interceptor;
+
+  // Data passed from DataSource::Trace() into the interceptor.
+  struct TracePacketCallbackArgs {
+    internal::DataSourceStaticState* static_state;
+    uint32_t instance_index;
+    protozero::ConstBytes packet_data;
+    ThreadLocalState* tls;
+  };
+
+  // These callback functions are defined as stateless to avoid accidentally
+  // introducing cross-thread data races.
+  using TLSFactory = std::unique_ptr<ThreadLocalState> (*)(
+      internal::DataSourceStaticState*,
+      uint32_t data_source_instance_index);
+  using TracePacketCallback = void (*)(TracePacketCallbackArgs);
+
+  static void RegisterImpl(
+      const InterceptorDescriptor& descriptor,
+      std::function<std::unique_ptr<InterceptorBase>()> factory,
+      InterceptorBase::TLSFactory tls_factory,
+      InterceptorBase::TracePacketCallback on_trace_packet);
+};
+
+// Templated interceptor instantiation. See above for usage.
+template <class InterceptorType>
+class PERFETTO_EXPORT Interceptor : public InterceptorBase {
+ public:
+  // A context object provided to the ThreadLocalState constructor. Provides
+  // access to the per-instance interceptor object.
+  class ThreadLocalStateArgs {
+   public:
+    ~ThreadLocalStateArgs() = default;
+
+    // Return a locked reference to the interceptor session. The session object
+    // will remain valid as long as the returned handle is in scope.
+    LockedHandle<InterceptorType> GetInterceptorLocked() {
+      auto* internal_state = static_state_->TryGet(data_source_instance_index_);
+      if (!internal_state)
+        return LockedHandle<InterceptorType>();
+      return LockedHandle<InterceptorType>(
+          &internal_state->lock,
+          static_cast<InterceptorType*>(internal_state->interceptor.get()));
+    }
+
+   private:
+    friend class Interceptor<InterceptorType>;
+    friend class InterceptorContext;
+    friend class TracingMuxerImpl;
+
+    ThreadLocalStateArgs(internal::DataSourceStaticState* static_state,
+                         uint32_t data_source_instance_index)
+        : static_state_(static_state),
+          data_source_instance_index_(data_source_instance_index) {}
+
+    internal::DataSourceStaticState* const static_state_;
+    const uint32_t data_source_instance_index_;
+  };
+
+  // A context object provided to each call into |OnTracePacket|. Contains the
+  // intercepted serialized trace packet data.
+  class InterceptorContext {
+   public:
+    InterceptorContext(InterceptorContext&&) noexcept = default;
+    ~InterceptorContext() = default;
+
+    // Return a locked reference to the interceptor session. The session object
+    // will remain valid as long as the returned handle is in scope.
+    LockedHandle<InterceptorType> GetInterceptorLocked() {
+      return tls_args_.GetInterceptorLocked();
+    }
+
+    // Return the thread-local state for this interceptor. See
+    // InterceptorBase::ThreadLocalState.
+    typename InterceptorType::ThreadLocalState& GetThreadLocalState() {
+      return static_cast<typename InterceptorType::ThreadLocalState&>(*tls_);
+    }
+
+    // A buffer containing the serialized TracePacket protocol buffer message.
+    // This memory is only valid during the call to OnTracePacket.
+    protozero::ConstBytes packet_data;
+
+   private:
+    friend class Interceptor<InterceptorType>;
+    InterceptorContext(TracePacketCallbackArgs args)
+        : packet_data(args.packet_data),
+          tls_args_(args.static_state, args.instance_index),
+          tls_(args.tls) {}
+    InterceptorContext(const InterceptorContext&) = delete;
+    InterceptorContext& operator=(const InterceptorContext&) = delete;
+
+    ThreadLocalStateArgs tls_args_;
+    InterceptorBase::ThreadLocalState* const tls_;
+  };
+
+  // Register the interceptor for use in tracing sessions.
+  // The optional |constructor_args| will be passed to the interceptor when it
+  // is constructed.
+  template <class... Args>
+  static void Register(const InterceptorDescriptor& descriptor,
+                       const Args&... constructor_args) {
+    auto factory = [constructor_args...]() {
+      return std::unique_ptr<InterceptorBase>(
+          new InterceptorType(constructor_args...));
+    };
+    auto tls_factory = [](internal::DataSourceStaticState* static_state,
+                          uint32_t data_source_instance_index) {
+      // Don't bother allocating TLS state unless the interceptor is actually
+      // using it.
+      if (std::is_same<typename InterceptorType::ThreadLocalState,
+                       InterceptorBase::ThreadLocalState>::value) {
+        return std::unique_ptr<InterceptorBase::ThreadLocalState>(nullptr);
+      }
+      ThreadLocalStateArgs args(static_state, data_source_instance_index);
+      return std::unique_ptr<InterceptorBase::ThreadLocalState>(
+          new typename InterceptorType::ThreadLocalState(args));
+    };
+    auto on_trace_packet = [](TracePacketCallbackArgs args) {
+      InterceptorType::OnTracePacket(InterceptorContext(std::move(args)));
+    };
+    RegisterImpl(descriptor, std::move(factory), std::move(tls_factory),
+                 std::move(on_trace_packet));
+  }
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_INTERCEPTOR_H_
diff --git a/include/perfetto/tracing/internal/data_source_internal.h b/include/perfetto/tracing/internal/data_source_internal.h
index 8e06ee4..6b0ba9c 100644
--- a/include/perfetto/tracing/internal/data_source_internal.h
+++ b/include/perfetto/tracing/internal/data_source_internal.h
@@ -34,6 +34,7 @@
 namespace perfetto {
 
 class DataSourceBase;
+class InterceptorBase;
 class TraceWriterBase;
 
 namespace internal {
@@ -69,6 +70,11 @@
   // source.
   TracingBackendId backend_id = 0;
 
+  // Each backend may connect to the tracing service multiple times if a
+  // disconnection occurs. This counter is used to uniquely identify each
+  // connection so that trace writers don't get reused across connections.
+  uint32_t backend_connection_id = 0;
+
   // The instance id as assigned by the tracing service. Note that because a
   // process can be connected to >1 services, this ID is not globally unique but
   // is only unique within the scope of its backend.
@@ -80,13 +86,23 @@
   // event).
   uint64_t config_hash = 0;
 
+  // If this data source is being intercepted (see Interceptor), this field
+  // contains the non-zero id of a registered interceptor which should receive
+  // trace packets for this session. Note: interceptor id 1 refers to the first
+  // element of TracingMuxerImpl::interceptors_ with successive numbers using
+  // the following slots.
+  uint32_t interceptor_id = 0;
+
   // This lock is not held to implement Trace() and it's used only if the trace
   // code wants to access its own data source state.
   // This is to prevent that accessing the data source on an arbitrary embedder
   // thread races with the internal IPC thread destroying the data source
   // because of a end-of-tracing notification from the service.
+  // This lock is also used to protect access to a possible interceptor for this
+  // data source session.
   std::recursive_mutex lock;
   std::unique_ptr<DataSourceBase> data_source;
+  std::unique_ptr<InterceptorBase> interceptor;
 };
 
 // This is to allow lazy-initialization and avoid static initializers and
@@ -131,13 +147,19 @@
     trace_writer.reset();
     incremental_state.reset();
     backend_id = 0;
+    backend_connection_id = 0;
     buffer_id = 0;
+    data_source_instance_id = 0;
+    is_intercepted = false;
   }
 
   std::unique_ptr<TraceWriterBase> trace_writer;
   IncrementalStatePointer incremental_state = {nullptr, [](void*) {}};
   TracingBackendId backend_id;
+  uint32_t backend_connection_id;
   BufferId buffer_id;
+  uint64_t data_source_instance_id;
+  bool is_intercepted;
 };
 
 // Per-DataSource-type thread-local state.
diff --git a/include/perfetto/tracing/internal/interceptor_trace_writer.h b/include/perfetto/tracing/internal/interceptor_trace_writer.h
new file mode 100644
index 0000000..5dd28cd
--- /dev/null
+++ b/include/perfetto/tracing/internal/interceptor_trace_writer.h
@@ -0,0 +1,68 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACING_INTERNAL_INTERCEPTOR_TRACE_WRITER_H_
+#define INCLUDE_PERFETTO_TRACING_INTERNAL_INTERCEPTOR_TRACE_WRITER_H_
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/tracing/interceptor.h"
+#include "perfetto/tracing/internal/basic_types.h"
+#include "perfetto/tracing/trace_writer_base.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace internal {
+
+// A heap-backed trace writer used to reroute trace packets to an interceptor.
+class InterceptorTraceWriter : public TraceWriterBase {
+ public:
+  InterceptorTraceWriter(std::unique_ptr<InterceptorBase::ThreadLocalState> tls,
+                         InterceptorBase::TracePacketCallback packet_callback,
+                         DataSourceStaticState* static_state,
+                         uint32_t instance_index);
+  ~InterceptorTraceWriter() override;
+
+  // TraceWriterBase implementation.
+  protozero::MessageHandle<protos::pbzero::TracePacket> NewTracePacket()
+      override;
+  void Flush(std::function<void()> callback = {}) override;
+  uint64_t written() const override;
+
+ private:
+  std::unique_ptr<InterceptorBase::ThreadLocalState> tls_;
+  InterceptorBase::TracePacketCallback packet_callback_;
+
+  protozero::HeapBuffered<protos::pbzero::TracePacket> cur_packet_;
+  uint64_t bytes_written_ = 0;
+
+  // Static state of the data source we are intercepting.
+  DataSourceStaticState* const static_state_;
+
+  // Index of the data source tracing session which we are intercepting
+  // (0...kMaxDataSourceInstances - 1). Used to look up this interceptor's
+  // session state (i.e., the Interceptor class instance) in the
+  // DataSourceStaticState::instances array.
+  const uint32_t instance_index_;
+
+  const uint32_t sequence_id_;
+
+  static std::atomic<uint32_t> next_sequence_id_;
+};
+
+}  // namespace internal
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_INTERNAL_INTERCEPTOR_TRACE_WRITER_H_
diff --git a/include/perfetto/tracing/internal/tracing_muxer.h b/include/perfetto/tracing/internal/tracing_muxer.h
index 4363ca5..a0b464c 100644
--- a/include/perfetto/tracing/internal/tracing_muxer.h
+++ b/include/perfetto/tracing/internal/tracing_muxer.h
@@ -22,6 +22,7 @@
 
 #include "perfetto/base/export.h"
 #include "perfetto/tracing/core/forward_decls.h"
+#include "perfetto/tracing/interceptor.h"
 #include "perfetto/tracing/internal/basic_types.h"
 #include "perfetto/tracing/internal/tracing_tls.h"
 #include "perfetto/tracing/platform.h"
@@ -68,6 +69,8 @@
   // projects this means "same thread"). Alternatively the client needs to take
   // care of using synchronization primitives to prevent concurrent accesses.
   virtual std::unique_ptr<TraceWriterBase> CreateTraceWriter(
+      DataSourceStaticState*,
+      uint32_t data_source_instance_index,
       DataSourceState*,
       BufferExhaustedPolicy buffer_exhausted_policy) = 0;
 
@@ -75,6 +78,12 @@
 
   uint32_t generation(std::memory_order ord) { return generation_.load(ord); }
 
+  using InterceptorFactory = std::function<std::unique_ptr<InterceptorBase>()>;
+  virtual void RegisterInterceptor(const InterceptorDescriptor&,
+                                   InterceptorFactory,
+                                   InterceptorBase::TLSFactory,
+                                   InterceptorBase::TracePacketCallback) = 0;
+
  protected:
   explicit TracingMuxer(Platform* platform) : platform_(platform) {}
 
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index bbae795..aa48467 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -91,25 +91,46 @@
   using Base = DataSource<DataSourceType, TrackEventDataSourceTraits>;
 
  public:
+  // Add or remove a session observer for this track event data source. The
+  // observer will be notified about started and stopped tracing sessions.
+  // Returns |true| if the observer was succesfully added (i.e., the maximum
+  // number of observers wasn't exceeded).
+  static bool AddSessionObserver(TrackEventSessionObserver* observer) {
+    return TrackEventInternal::AddSessionObserver(observer);
+  }
+
+  static void RemoveSessionObserver(TrackEventSessionObserver* observer) {
+    TrackEventInternal::RemoveSessionObserver(observer);
+  }
+
   // DataSource implementation.
   void OnSetup(const DataSourceBase::SetupArgs& args) override {
     auto config_raw = args.config->track_event_config_raw();
     bool ok = config_.ParseFromArray(config_raw.data(), config_raw.size());
     PERFETTO_DCHECK(ok);
-    TrackEventInternal::EnableTracing(*Registry, config_,
-                                      args.internal_instance_index);
+    TrackEventInternal::EnableTracing(*Registry, config_, args);
   }
 
-  void OnStart(const DataSourceBase::StartArgs&) override {}
+  void OnStart(const DataSourceBase::StartArgs& args) override {
+    TrackEventInternal::OnStart(args);
+  }
 
   void OnStop(const DataSourceBase::StopArgs& args) override {
-    TrackEventInternal::DisableTracing(*Registry, args.internal_instance_index);
+    TrackEventInternal::DisableTracing(*Registry, args);
   }
 
   static void Flush() {
     Base::template Trace([](typename Base::TraceContext ctx) { ctx.Flush(); });
   }
 
+  // Determine if *any* tracing category is enabled.
+  static bool IsEnabled() {
+    bool enabled = false;
+    Base::template CallIfEnabled(
+        [&](uint32_t /*instances*/) { enabled = true; });
+    return enabled;
+  }
+
   // Determine if tracing for the given static category is enabled.
   template <size_t CategoryIndex>
   static bool IsCategoryEnabled() {
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 6de74ef..cfd2be2 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -20,6 +20,7 @@
 #include "perfetto/base/flat_set.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/tracing/core/forward_decls.h"
+#include "perfetto/tracing/data_source.h"
 #include "perfetto/tracing/debug_annotation.h"
 #include "perfetto/tracing/trace_writer_base.h"
 #include "perfetto/tracing/track.h"
@@ -31,6 +32,7 @@
 
 namespace perfetto {
 class EventContext;
+class TrackEventSessionObserver;
 struct Category;
 namespace protos {
 namespace gen {
@@ -41,15 +43,34 @@
 }  // namespace pbzero
 }  // namespace protos
 
+// A callback interface for observing track event tracing sessions starting and
+// stopping. See TrackEvent::{Add,Remove}SessionObserver. Note that all methods
+// will be called on an internal Perfetto thread.
+class TrackEventSessionObserver {
+ public:
+  virtual ~TrackEventSessionObserver();
+  // Called when a track event tracing session is configured. Note tracing isn't
+  // active yet, so track events emitted here won't be recorded. See
+  // DataSourceBase::OnSetup.
+  virtual void OnSetup(const DataSourceBase::SetupArgs&);
+  // Called when a track event tracing session is started. It is possible to
+  // emit track events from this callback.
+  virtual void OnStart(const DataSourceBase::StartArgs&);
+  // Called when a track event tracing session is stopped. It is still possible
+  // to emit track events from this callback.
+  virtual void OnStop(const DataSourceBase::StopArgs&);
+};
+
 namespace internal {
 class TrackEventCategoryRegistry;
 
-class BaseTrackEventInternedDataIndex {
+class PERFETTO_EXPORT BaseTrackEventInternedDataIndex {
  public:
   virtual ~BaseTrackEventInternedDataIndex();
 
 #if PERFETTO_DCHECK_IS_ON()
   const char* type_id_ = nullptr;
+  const void* add_function_ptr_ = nullptr;
 #endif  // PERFETTO_DCHECK_IS_ON()
 };
 
@@ -98,11 +119,15 @@
       const TrackEventCategoryRegistry&,
       bool (*register_data_source)(const DataSourceDescriptor&));
 
+  static bool AddSessionObserver(TrackEventSessionObserver*);
+  static void RemoveSessionObserver(TrackEventSessionObserver*);
+
   static void EnableTracing(const TrackEventCategoryRegistry& registry,
                             const protos::gen::TrackEventConfig& config,
-                            uint32_t instance_index);
+                            const DataSourceBase::SetupArgs&);
+  static void OnStart(const DataSourceBase::StartArgs&);
   static void DisableTracing(const TrackEventCategoryRegistry& registry,
-                             uint32_t instance_index);
+                             const DataSourceBase::StopArgs&);
   static bool IsCategoryEnabled(const TrackEventCategoryRegistry& registry,
                                 const protos::gen::TrackEventConfig& config,
                                 const Category& category);
@@ -150,7 +175,7 @@
 
   // Get the clock used by GetTimeNs().
   static constexpr protos::pbzero::BuiltinClock GetClockId() {
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
     return protos::pbzero::BUILTIN_CLOCK_BOOTTIME;
 #else
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index b5b6303..31908df 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -26,7 +26,9 @@
 #include "perfetto/tracing/track_event_category_registry.h"
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 // Defines data structures for backing a category registry.
 //
@@ -105,18 +107,22 @@
     namespace tns = ::PERFETTO_TRACK_EVENT_NAMESPACE;                     \
     /* Compute the category index outside the lambda to work around a */  \
     /* GCC 7 bug */                                                       \
-    constexpr auto PERFETTO_UID(kCatIndex) =                              \
+    constexpr auto PERFETTO_UID(                                          \
+        kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_) =          \
         PERFETTO_GET_CATEGORY_INDEX(category);                            \
     if (tns::internal::IsDynamicCategory(category)) {                     \
       tns::TrackEvent::CallIfEnabled([&](uint32_t instances) {            \
-        tns::TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>(       \
+        tns::TrackEvent::TraceForCategory<PERFETTO_UID(                   \
+            kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_)>(      \
             instances, category, ##__VA_ARGS__);                          \
       });                                                                 \
     } else {                                                              \
-      tns::TrackEvent::CallIfCategoryEnabled<PERFETTO_UID(kCatIndex)>(    \
+      tns::TrackEvent::CallIfCategoryEnabled<PERFETTO_UID(                \
+          kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_)>(        \
           [&](uint32_t instances) {                                       \
             /* TODO(skyostil): Get rid of the category name parameter. */ \
-            tns::TrackEvent::TraceForCategory<PERFETTO_UID(kCatIndex)>(   \
+            tns::TrackEvent::TraceForCategory<PERFETTO_UID(               \
+                kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_)>(  \
                 instances, nullptr, ##__VA_ARGS__);                       \
           });                                                             \
     }                                                                     \
diff --git a/include/perfetto/tracing/platform.h b/include/perfetto/tracing/platform.h
index 662e73d..ecd74d9 100644
--- a/include/perfetto/tracing/platform.h
+++ b/include/perfetto/tracing/platform.h
@@ -22,6 +22,7 @@
 
 #include <functional>
 #include <memory>
+#include <string>
 
 #include "perfetto/base/export.h"
 
diff --git a/include/perfetto/tracing/tracing.h b/include/perfetto/tracing/tracing.h
index b661b3d..c63cfe3 100644
--- a/include/perfetto/tracing/tracing.h
+++ b/include/perfetto/tracing/tracing.h
@@ -60,6 +60,25 @@
   kCustomBackend = 1 << 2,
 };
 
+struct TracingError {
+  enum ErrorCode : uint32_t {
+    // Peer disconnection.
+    kDisconnected = 1,
+
+    // The Start() method failed. This is typically because errors in the passed
+    // TraceConfig. More details are available in |message|.
+    kTracingFailed = 2,
+  };
+
+  ErrorCode code;
+  std::string message;
+
+  TracingError(ErrorCode cd, std::string msg)
+      : code(cd), message(std::move(msg)) {
+    PERFETTO_CHECK(!message.empty());
+  }
+};
+
 struct TracingInitArgs {
   uint32_t backends = 0;                     // One or more BackendFlags.
   TracingBackend* custom_backend = nullptr;  // [Optional].
@@ -83,6 +102,21 @@
   // Must be one of [4, 8, 16, 32].
   uint32_t shmem_page_size_hint_kb = 0;
 
+  // [Optional] The length of the period during which shared-memory-buffer
+  // chunks that have been filled with data are accumulated (batched) on the
+  // producer side, before the service is notified of them over an out-of-band
+  // IPC call. If, while this period lasts, the shared memory buffer gets too
+  // full, the IPC call will be sent immediately. The value of this parameter is
+  // a trade-off between IPC traffic overhead and the ability to sustain bursts
+  // of trace writes. The higher the value, the more chunks will be batched and
+  // the less buffer space will be available to hide the latency of the service,
+  // and vice versa. For more details, see the SetBatchCommitsDuration method in
+  // shared_memory_arbiter.h.
+  //
+  // Note: With the default value of 0ms, batching still happens but with a zero
+  // delay, i.e. commits will be sent to the service at the next opportunity.
+  uint32_t shmem_batch_commits_duration_ms = 0;
+
  protected:
   friend class Tracing;
   friend class internal::TracingMuxerImpl;
@@ -176,6 +210,41 @@
   // thread.
   virtual void SetOnStartCallback(std::function<void()>) = 0;
 
+  // This callback can be used to get a notification when some error occured
+  // (e.g., peer disconnection). Error type will be passed as an argument. This
+  // callback will be invoked on an internal perfetto thread.
+  virtual void SetOnErrorCallback(std::function<void(TracingError)>) = 0;
+
+  // Issues a flush request, asking all data sources to ack the request, within
+  // the specified timeout. A "flush" is a fence to ensure visibility of data in
+  // the async tracing pipeline. It guarantees that all data written before the
+  // Flush() call will be visible in the trace buffer and hence by the
+  // ReadTrace() / ReadTraceBlocking() methods.
+  // Args:
+  //  callback: will be invoked on an internal perfetto thread when all data
+  //    sources have acked, or the timeout is reached. The bool argument
+  //    will be true if all data sources acked within the timeout, false if
+  //    the timeout was hit or some other error occurred (e.g. the tracing
+  //    session wasn't started or ended).
+  //  timeout_ms: how much time the service will wait for data source acks. If
+  //    0, the global timeout specified in the TraceConfig (flush_timeout_ms)
+  //    will be used. If flush_timeout_ms is also unspecified, a default value
+  //    of 5s will be used.
+  // Known issues:
+  //    Because flushing is still based on service-side scraping, the very last
+  //    trace packet for each data source thread will not be visible. Fixing
+  //    this requires either propagating the Flush() to the data sources or
+  //    changing the order of atomic operations in the service (b/162206162).
+  //    Until then, a workaround is to make sure to call
+  //    DataSource::Trace([](TraceContext ctx) { ctx.Flush(); }) just before
+  //    stopping, on each thread where DataSource::Trace has been previously
+  //    called.
+  virtual void Flush(std::function<void(bool)>, uint32_t timeout_ms = 0) = 0;
+
+  // Blocking version of Flush(). Waits until all data sources have acked and
+  // returns the success/failure status.
+  bool FlushBlocking(uint32_t timeout_ms = 0);
+
   // Disable tracing asynchronously.
   // Use SetOnStopCallback() to get a notification when the tracing session is
   // fully stopped and all data sources have acked.
@@ -190,6 +259,12 @@
   // This callback will be invoked on an internal perfetto thread.
   virtual void SetOnStopCallback(std::function<void()>) = 0;
 
+  // Changes the TraceConfig for an active tracing session. The session must
+  // have been configured and started before. Note that the tracing service
+  // only supports changing a subset of TraceConfig fields,
+  // see ConsumerEndpoint::ChangeTraceConfig().
+  virtual void ChangeTraceConfig(const TraceConfig&) = 0;
+
   // Struct passed as argument to the callback passed to ReadTrace().
   // [data, size] is guaranteed to contain 1 or more full trace packets, which
   // can be decoded using trace.proto. No partial or truncated packets are
@@ -207,8 +282,13 @@
 
   // Reads back the trace data (raw protobuf-encoded bytes) asynchronously.
   // Can be called at any point during the trace, typically but not necessarily,
-  // after stopping. Reading the trace data is a destructive operation w.r.t.
-  // contents of the trace buffer and is not idempotent.
+  // after stopping. If this is called before the end of the trace (i.e. before
+  // Stop() / StopBlocking()), in almost all cases you need to call
+  // Flush() / FlushBlocking() before Read(). This is to guarantee that tracing
+  // data in-flight in the data sources is committed into the tracing buffers
+  // before reading them.
+  // Reading the trace data is a destructive operation w.r.t. contents of the
+  // trace buffer and is not idempotent.
   // A single ReadTrace() call can yield >1 callback invocations, until
   // |has_more| is false.
   using ReadTraceCallback = std::function<void(ReadTraceCallbackArgs)>;
@@ -241,6 +321,30 @@
 
   // Synchronous version of GetTraceStats() for convenience.
   GetTraceStatsCallbackArgs GetTraceStatsBlocking();
+
+  // Struct passed as an argument to the callback for QueryServiceState().
+  // Contains information about registered data sources.
+  struct QueryServiceStateCallbackArgs {
+    // Whether or not getting the service state succeeded.
+    bool success = false;
+    // Serialized TracingServiceState protobuf message. To decode:
+    //
+    //   perfetto::protos::gen::TracingServiceState state;
+    //   state.ParseFromArray(args.service_state_data.data(),
+    //                        args.service_state_data.size());
+    //
+    std::vector<uint8_t> service_state_data;
+  };
+
+  // Requests a snapshot of the tracing service state for this session. Only one
+  // request per session may be active at a time. This callback will be invoked
+  // on an internal perfetto thread.
+  using QueryServiceStateCallback =
+      std::function<void(QueryServiceStateCallbackArgs)>;
+  virtual void QueryServiceState(QueryServiceStateCallback) = 0;
+
+  // Synchronous version of QueryServiceState() for convenience.
+  QueryServiceStateCallbackArgs QueryServiceStateBlocking();
 };
 
 }  // namespace perfetto
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 8eebf76..c8293e4 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -218,7 +218,9 @@
       perfetto::internal::TrackEventDataSourceTraits)
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 // Ensure that |string| is a static constant string.
 //
diff --git a/include/perfetto/tracing/track_event_interned_data_index.h b/include/perfetto/tracing/track_event_interned_data_index.h
index cce2665..1afcdd0 100644
--- a/include/perfetto/tracing/track_event_interned_data_index.h
+++ b/include/perfetto/tracing/track_event_interned_data_index.h
@@ -208,6 +208,21 @@
               "%s. New type: %s.",
               entry.second->type_id_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
         }
+        // If an interned data index is defined in an anonymous namespace, we
+        // can end up with multiple copies of it in the same program. Because
+        // they will all share a memory address through TLS, this can lead to
+        // subtle data corruption if all the copies aren't exactly identical.
+        // Try to detect this by checking if the Add() function address remains
+        // constant.
+        if (reinterpret_cast<void*>(&InternedDataType::Add) !=
+            entry.second->add_function_ptr_) {
+          PERFETTO_FATAL(
+              "Inconsistent interned data index. Maybe the index was defined "
+              "in an anonymous namespace in a header or copied to multiple "
+              "files? Duplicate index definitions can lead to memory "
+              "corruption! Type id: %s",
+              entry.second->type_id_);
+        }
 #endif  // PERFETTO_DCHECK_IS_ON()
         return reinterpret_cast<InternedDataType*>(entry.second.get());
       }
@@ -219,6 +234,8 @@
         entry.second.reset(new InternedDataType());
 #if PERFETTO_DCHECK_IS_ON()
         entry.second->type_id_ = PERFETTO_DEBUG_FUNCTION_IDENTIFIER();
+        entry.second->add_function_ptr_ =
+            reinterpret_cast<void*>(&InternedDataType::Add);
 #endif  // PERFETTO_DCHECK_IS_ON()
         return reinterpret_cast<InternedDataType*>(entry.second.get());
       }
diff --git a/include/perfetto/tracing/track_event_legacy.h b/include/perfetto/tracing/track_event_legacy.h
index 394f431..de2b74b 100644
--- a/include/perfetto/tracing/track_event_legacy.h
+++ b/include/perfetto/tracing/track_event_legacy.h
@@ -32,7 +32,9 @@
 #endif
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 // ----------------------------------------------------------------------------
 // Constants.
diff --git a/include/perfetto/tracing/track_event_state_tracker.h b/include/perfetto/tracing/track_event_state_tracker.h
new file mode 100644
index 0000000..7b2437e
--- /dev/null
+++ b/include/perfetto/tracing/track_event_state_tracker.h
@@ -0,0 +1,148 @@
+/*
+ * 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 INCLUDE_PERFETTO_TRACING_TRACK_EVENT_STATE_TRACKER_H_
+#define INCLUDE_PERFETTO_TRACING_TRACK_EVENT_STATE_TRACKER_H_
+
+#include "perfetto/base/export.h"
+
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace perfetto {
+namespace protos {
+namespace pbzero {
+class TracePacket_Decoder;
+class TrackEvent;
+class TrackEvent_Decoder;
+}  // namespace pbzero
+}  // namespace protos
+
+// A helper for keeping track of incremental state when intercepting track
+// events.
+class PERFETTO_EXPORT TrackEventStateTracker {
+ public:
+  ~TrackEventStateTracker();
+
+  struct StackFrame {
+    uint64_t timestamp{};
+
+    // Only one of |name| and |name_iid| will be set.
+    std::string name;
+    uint64_t name_iid{};
+    uint64_t name_hash{};
+
+    // Only one of |category| and |category_iid| will be set.
+    std::string category;
+    uint64_t category_iid{};
+  };
+
+  struct Track {
+    uint64_t uuid{};
+    uint32_t index{};  // Ordinal number for the track in the tracing session.
+
+    std::string name;
+    int64_t pid{};
+    int64_t tid{};
+
+    // Opaque user data associated with the track.
+    std::vector<uint8_t> user_data;
+
+    // Stack of opened slices on this track.
+    std::vector<StackFrame> stack;
+  };
+
+  // State for a single trace writer sequence (typically a single thread).
+  struct SequenceState {
+    // Trace packet sequence defaults.
+    Track track;
+
+    // Interned state.
+#if PERFETTO_DCHECK_IS_ON()
+    uint32_t sequence_id{};
+#endif
+    std::map<uint64_t /*iid*/, std::string> event_names;
+    std::map<uint64_t /*iid*/, std::string> event_categories;
+    std::map<uint64_t /*iid*/, std::string> debug_annotation_names;
+  };
+
+  // State for the entire tracing session. Shared by all trace writer sequences
+  // participating in the session.
+  struct SessionState {
+    // Non-thread-bound tracks.
+    std::map<uint64_t /*uuid*/, Track> tracks;
+  };
+
+  // Represents a single decoded track event (without arguments).
+  struct ParsedTrackEvent {
+    explicit ParsedTrackEvent(
+        const perfetto::protos::pbzero::TrackEvent::Decoder&);
+
+    // Underlying event.
+    const perfetto::protos::pbzero::TrackEvent::Decoder& track_event;
+
+    // Event metadata.
+    uint64_t timestamp_ns{};
+    uint64_t duration_ns{};
+
+    size_t stack_depth{};
+
+    protozero::ConstChars category{};
+    protozero::ConstChars name{};
+    uint64_t name_hash{};
+  };
+
+  // Interface used by the tracker to access tracing session and sequence state
+  // and to report parsed track events.
+  class Delegate {
+   public:
+    virtual ~Delegate();
+
+    // Called to retrieve the session-global state shared by all sequences. The
+    // returned pointer must remain valid (locked) throughout the call to
+    // |ProcessTracePacket|.
+    virtual SessionState* GetSessionState() = 0;
+
+    // Called when the metadata (e.g., name) for a track changes. |Track| can be
+    // modified by the callback to attach user data.
+    virtual void OnTrackUpdated(Track&) = 0;
+
+    // If the packet given to |ProcessTracePacket| contains a track event, this
+    // method is called to report the properties of that event. Note that memory
+    // pointers in |TrackEvent| will only be valid during this call.
+    virtual void OnTrackEvent(const Track&, const ParsedTrackEvent&) = 0;
+  };
+
+  // Process a single trace packet, reporting any contained track event back via
+  // the delegate interface. |SequenceState| must correspond to the sequence
+  // that was used to write the packet.
+  static void ProcessTracePacket(Delegate&,
+                                 SequenceState&,
+                                 const protos::pbzero::TracePacket_Decoder&);
+
+ private:
+  static void UpdateIncrementalState(
+      Delegate&,
+      SequenceState&,
+      const protos::pbzero::TracePacket_Decoder&);
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_STATE_TRACKER_H_
diff --git a/infra/ci/frontend/static/script.js b/infra/ci/frontend/static/script.js
index 0d530c4..9f56e3d 100644
--- a/infra/ci/frontend/static/script.js
+++ b/infra/ci/frontend/static/script.js
@@ -32,16 +32,8 @@
   { id: 'android-clang-arm-asan', label: 'asan' },
 ];
 
-// Chart IDs from the Stackdriver daashboard
-// https://app.google.stackdriver.com/dashboards/5008687313278081798?project=perfetto-ci.
-const STATS_CHART_IDS = [
-  '2617092544855936024',   // Job queue len.
-  '15349606823829051218',  // Workers CPU usage
-  '2339121267466167448',   // E2E CL test time (median).
-  '6055813426334723906',   // Job run time (median).
-  '13112965165933080534',  // Job queue time (P95).
-  '2617092544855936456',   // Job run time (P95).
-];
+const STATS_LINK =
+    'https://app.google.stackdriver.com/dashboards/5008687313278081798?project=perfetto-ci';
 
 const state = {
   // An array of recent CL objects retrieved from Gerrit.
@@ -113,7 +105,6 @@
     '/logs/:jobId': LogsPageRenderer,
     '/jobs': JobsPageRenderer,
     '/jobs/:jobId': JobsPageRenderer,
-    '/stats/:period': StatsPageRenderer,
   });
 
   setInterval(fetchGerritCLs, 15000);
@@ -130,20 +121,25 @@
   const logUrl = 'https://goto.google.com/perfetto-ci-logs-';
   const docsUrl =
       'https://perfetto.dev/docs/design-docs/continuous-integration';
-  return m('header',
-    m('a[href=/#!/cls]', m('h1', 'Perfetto ', m('span', 'CI'))),
-    m('nav',
-      m(`div${active('cls')}`, m('a[href=/#!/cls]', 'CLs')),
-      m(`div${active('jobs')}`, m('a[href=/#!/jobs]', 'Jobs')),
-      m(`div${active('stats')}`, m('a[href=/#!/stats/1d]', 'Stats')),
-      m(`div`, m(`a[href=${docsUrl}][target=_blank]`, 'Docs')),
-      m(`div.logs`, 'Logs',
-        m('div', m(`a[href=${logUrl}controller][target=_blank]`, 'Controller')),
-        m('div', m(`a[href=${logUrl}workers][target=_blank]`, 'Workers')),
-        m('div', m(`a[href=${logUrl}frontend][target=_blank]`, 'Frontend')),
-      ),
-    )
-  );
+  return m(
+      'header', m('a[href=/#!/cls]', m('h1', 'Perfetto ', m('span', 'CI'))),
+      m(
+          'nav',
+          m(`div${active('cls')}`, m('a[href=/#!/cls]', 'CLs')),
+          m(`div${active('jobs')}`, m('a[href=/#!/jobs]', 'Jobs')),
+          m(`div${active('stats')}`,
+            m(`a[href=${STATS_LINK}][target=_blank]`, 'Stats')),
+          m(`div`, m(`a[href=${docsUrl}][target=_blank]`, 'Docs')),
+          m(
+              `div.logs`,
+              'Logs',
+              m('div',
+                m(`a[href=${logUrl}controller][target=_blank]`, 'Controller')),
+              m('div', m(`a[href=${logUrl}workers][target=_blank]`, 'Workers')),
+              m('div',
+                m(`a[href=${logUrl}frontend][target=_blank]`, 'Frontend')),
+              ),
+          ));
 }
 
 var CLsPageRenderer = {
@@ -374,7 +370,8 @@
 const TermRenderer = {
   oncreate: function (vnode) {
     console.log('Creating terminal object');
-    term = new Terminal({ rows: 6, fontSize: 12, scrollback: 100000 });
+    term = new Terminal(
+        {rows: 6, fontFamily: 'monospace', fontSize: 12, scrollback: 100000});
     term.open(vnode.dom);
     term.fit();
     if (vnode.attrs.focused) term.focus();
@@ -535,28 +532,6 @@
   }
 };
 
-const StatsPageRenderer = {
-  view: function (vnode) {
-    const makeIframe = id => {
-      let url = 'https://public.google.stackdriver.com/public/chart';
-      url += `/${id}?timeframe=${vnode.attrs.period}`;
-      url += '&drawMode=color&showLegend=false&theme=light';
-      return m('iframe', {
-        src: url,
-        scrolling: 'no',
-        seamless: 'seamless',
-      });
-    };
-
-    return [
-      renderHeader(),
-      m('main#stats',
-        m('.stats-grid', STATS_CHART_IDS.map(makeIframe))
-      )
-    ];
-  }
-}
-
 // -----------------------------------------------------------------------------
 // Business logic (handles fetching from Gerrit and Firebase DB).
 // -----------------------------------------------------------------------------
diff --git a/infra/ci/frontend/static/style.css b/infra/ci/frontend/static/style.css
index 5ce48b2..429c761 100644
--- a/infra/ci/frontend/static/style.css
+++ b/infra/ci/frontend/static/style.css
@@ -4,7 +4,6 @@
 
 html {
     font-size: 13px;
-    font-weight: 300;
     padding: 0;
     margin: 0;
     height: 100%;
@@ -200,7 +199,6 @@
 .main-table thead {
     color: #111;
     font-size: 14px;
-    font-weight: 300;
 }
 
 .main-table thead tr:nth-child(n+3) td {
@@ -296,7 +294,7 @@
     height: 25px;
     line-height: 25px;
     font-size: 13px;
-    font-weight: 300;
+    -webkit-font-smoothing: antialiased;
 }
 
 .main-table tbody tr.nested td:first-child, .main-table tbody tr.nested a {
diff --git a/infra/ci/sandbox/Dockerfile b/infra/ci/sandbox/Dockerfile
index c687830..513c515 100644
--- a/infra/ci/sandbox/Dockerfile
+++ b/infra/ci/sandbox/Dockerfile
@@ -37,6 +37,7 @@
     pip install --quiet protobuf; \
     curl https://bootstrap.pypa.io/get-pip.py | python3 -; \
     pip3 install --quiet protobuf; \
+    pip3 install --quiet pandas; \
     curl -LO https://github.com/bazelbuild/bazel/releases/download/0.29.1/bazel-0.29.1-installer-linux-x86_64.sh; \
     chmod +x bazel-*-installer-linux-x86_64.sh; \
     ./bazel-*-installer-linux-x86_64.sh; \
diff --git a/infra/perfetto-get.appspot.com/main.py b/infra/perfetto-get.appspot.com/main.py
index 71e2b12..e4b14a8 100644
--- a/infra/perfetto-get.appspot.com/main.py
+++ b/infra/perfetto-get.appspot.com/main.py
@@ -37,6 +37,7 @@
 class GitilesMirrorHandler(webapp2.RequestHandler):
 
   def get(self, resource):
+    self.response.headers['Content-Type'] = 'text/plain'
     resource = resource.lower()
     if resource not in RESOURCES:
       self.error(404)
@@ -55,7 +56,6 @@
         return
       contents = base64.b64decode(result.content)
       memcache.set(url, contents, time=3600)  # 1h
-    self.response.headers['Content-Type'] = 'text/plain'
     self.response.headers['Content-Disposition'] = \
         'attachment; filename="%s"' % resource
     self.response.write(contents)
@@ -63,6 +63,6 @@
 
 app = webapp2.WSGIApplication([
     ('/', RedirectHandler),
-    ('/(.*)', GitilesMirrorHandler),
+    ('/([a-zA-Z0-9_.-]+)', GitilesMirrorHandler),
 ],
                               debug=True)
diff --git a/infra/perfetto.dev/BUILD.gn b/infra/perfetto.dev/BUILD.gn
index ddab3db..52351b6 100644
--- a/infra/perfetto.dev/BUILD.gn
+++ b/infra/perfetto.dev/BUILD.gn
@@ -168,6 +168,14 @@
   }
 }
 
+all_proto_deps = "$target_out_dir/proto.deps"
+exec_script("../../gn/standalone/glob.py",
+            [
+              "--root=" + rebase_path("../../protos", root_build_dir),
+              "--filter=*.proto",
+              "--output=" + rebase_path(all_proto_deps),
+            ])
+
 # Generates a .md file from a .proto.
 # Args:
 #  proto_src: [in]
@@ -181,6 +189,7 @@
       invoker.proto_src,
       js_src,
     ]
+    inputs += read_file(all_proto_deps, "list lines")
     outputs = [ invoker.markdown_out ]
     args = [
       "--path=$nodejs_bin",
diff --git a/infra/perfetto.dev/package-lock.json b/infra/perfetto.dev/package-lock.json
index 9f6302e..3c3f329 100644
--- a/infra/perfetto.dev/package-lock.json
+++ b/infra/perfetto.dev/package-lock.json
@@ -1071,9 +1071,9 @@
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
     },
     "highlight.js": {
-      "version": "9.18.1",
-      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz",
-      "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==",
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz",
+      "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==",
       "dev": true
     },
     "hosted-git-info": {
diff --git a/infra/perfetto.dev/package.json b/infra/perfetto.dev/package.json
index 791be22..1607812 100644
--- a/infra/perfetto.dev/package.json
+++ b/infra/perfetto.dev/package.json
@@ -13,7 +13,7 @@
     "concurrently": "^3.5.0",
     "ejs": "^3.0.1",
     "fs-extra": "^9.0.0",
-    "highlight.js": "^9.18.1",
+    "highlight.js": "^10.1.2",
     "marked": "^0.8.0",
     "node-sass": "^4.13.1",
     "onchange": "^6.1.0",
diff --git a/infra/perfetto.dev/src/gen_proto_reference.js b/infra/perfetto.dev/src/gen_proto_reference.js
index 044929e..c3d0481 100644
--- a/infra/perfetto.dev/src/gen_proto_reference.js
+++ b/infra/perfetto.dev/src/gen_proto_reference.js
@@ -26,13 +26,30 @@
 
 const visited = {};
 
+// This function is used to escape:
+// - The message-level comment, which becomes a full paragraph.
+// - The per-field comments, rendered as as table.
+function escapeCommentCommon(comment) {
+  comment = comment || '';
 
+  // Remove Next id: NN lines.
+  comment = comment.replace(/(\n)?^\s*next.*\bid:.*$/img, '');
+
+  // Hide our little dirty secrets.
+  comment = comment.replace(/(\n)?^\s*TODO\(\w+\):.*$/img, '');
+
+  // Turn |variable| references into `variable`.
+  comment = comment.replace(/[|](\w+?)[|]/g, '`$1`');
+  return comment;
+}
+
+// This is used to escape only the per-field comments.
 // Removes \n due to 80col wrapping and preserves only end-of-sentence line
 // breaks.
 function singleLineComment(comment) {
-  comment = comment || '';
+  comment = escapeCommentCommon(comment);
   comment = comment.trim();
-  comment = comment.replace(/\.\n/g, '<br>');
+  comment = comment.replace(/([.:?!])\n/g, '$1<br>');
   comment = comment.replace(/\n/g, ' ');
   return comment;
 }
@@ -62,7 +79,8 @@
   md += '\n';
   const fileName = path.basename(pType.filename);
   const relPath = path.relative(PROJECT_ROOT, pType.filename);
-  md += `${(pType.comment || '').replace(/(\n)?^\s*next.*\bid:.*$/img, '')}`;
+
+  md += escapeCommentCommon(pType.comment);
   md += `\n\nDefined in [${fileName}](/${relPath})\n\n`;
 
   const subTypes = [];
diff --git a/perfetto.rc b/perfetto.rc
index a33bd03..39f6ffc 100644
--- a/perfetto.rc
+++ b/perfetto.rc
@@ -25,8 +25,8 @@
     class late_start
     disabled
     user nobody
-    # Despite the "log" group below, traced_probes is whitelisted for log read
-    # access only on userdebug/eng via selinux (see traced_probes.te).
+    # Despite the "log" group below, traced_probes is allowed to read log
+    # only on userdebug/eng via selinux (see traced_probes.te).
     group nobody readproc log
     writepid /dev/cpuset/system-background/tasks
     # Clean up procfs configuration even if traced_probes crashes
@@ -50,12 +50,14 @@
 on property:persist.traced.enable=1
     # Trace files need to be:
     # - Written by either uid:shell or uid:statsd.
-    # - Read by shell and dropbox (dropbox is part of system_server).
-    # When written to dropbox, they are persistet in the perfetto-traces folder
-    # only for the time it takes to make a dropbox call, and unlinked
-    # immediately in any case.
+    # - Read by shell and incidentd.
     mkdir /data/misc/perfetto-traces 0773 root shell
 
+    # This directory allows shell to save configs file in a place where the
+    # perfetto cmdline client can read then. /data/local/tmp/ isn't safe because
+    # too many other domains can write into that. See b/170404111.
+    mkdir /data/misc/perfetto-configs 0775 root shell
+
     start traced
     start traced_probes
 
diff --git a/protos/perfetto/common/BUILD.gn b/protos/perfetto/common/BUILD.gn
index 90dde62..9dfde1b 100644
--- a/protos/perfetto/common/BUILD.gn
+++ b/protos/perfetto/common/BUILD.gn
@@ -25,6 +25,7 @@
     "data_source_descriptor.proto",
     "descriptor.proto",
     "gpu_counter_descriptor.proto",
+    "interceptor_descriptor.proto",
     "observable_events.proto",
     "sys_stats_counters.proto",
     "trace_stats.proto",
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/protos/perfetto/common/interceptor_descriptor.proto
similarity index 69%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to protos/perfetto/common/interceptor_descriptor.proto
index e22189e..e5f7a00 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/protos/perfetto/common/interceptor_descriptor.proto
@@ -18,10 +18,8 @@
 
 package perfetto.protos;
 
-import "protos/perfetto/metrics/custom_options.proto";
-
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+// Used to configure the properties of a data source interceptor.
+message InterceptorDescriptor {
+  // e.g., "console", "etw"
+  optional string name = 1;
 }
diff --git a/protos/perfetto/common/sys_stats_counters.proto b/protos/perfetto/common/sys_stats_counters.proto
index 837656b..1605a73 100644
--- a/protos/perfetto/common/sys_stats_counters.proto
+++ b/protos/perfetto/common/sys_stats_counters.proto
@@ -157,4 +157,37 @@
   VMSTAT_NR_ZSPAGES = 93;
   VMSTAT_NR_ION_HEAP = 94;
   VMSTAT_NR_GPU_HEAP = 95;
+  VMSTAT_ALLOCSTALL_DMA = 96;
+  VMSTAT_ALLOCSTALL_MOVABLE = 97;
+  VMSTAT_ALLOCSTALL_NORMAL = 98;
+  VMSTAT_COMPACT_DAEMON_FREE_SCANNED = 99;
+  VMSTAT_COMPACT_DAEMON_MIGRATE_SCANNED = 100;
+  VMSTAT_NR_FASTRPC = 101;
+  VMSTAT_NR_INDIRECTLY_RECLAIMABLE = 102;
+  VMSTAT_NR_ION_HEAP_POOL = 103;
+  VMSTAT_NR_KERNEL_MISC_RECLAIMABLE = 104;
+  VMSTAT_NR_SHADOW_CALL_STACK_BYTES = 105;
+  VMSTAT_NR_SHMEM_HUGEPAGES = 106;
+  VMSTAT_NR_SHMEM_PMDMAPPED = 107;
+  VMSTAT_NR_UNRECLAIMABLE_PAGES = 108;
+  VMSTAT_NR_ZONE_ACTIVE_ANON = 109;
+  VMSTAT_NR_ZONE_ACTIVE_FILE = 110;
+  VMSTAT_NR_ZONE_INACTIVE_ANON = 111;
+  VMSTAT_NR_ZONE_INACTIVE_FILE = 112;
+  VMSTAT_NR_ZONE_UNEVICTABLE = 113;
+  VMSTAT_NR_ZONE_WRITE_PENDING = 114;
+  VMSTAT_OOM_KILL = 115;
+  VMSTAT_PGLAZYFREE = 116;
+  VMSTAT_PGLAZYFREED = 117;
+  VMSTAT_PGREFILL = 118;
+  VMSTAT_PGSCAN_DIRECT = 119;
+  VMSTAT_PGSCAN_KSWAPD = 120;
+  VMSTAT_PGSKIP_DMA = 121;
+  VMSTAT_PGSKIP_MOVABLE = 122;
+  VMSTAT_PGSKIP_NORMAL = 123;
+  VMSTAT_PGSTEAL_DIRECT = 124;
+  VMSTAT_PGSTEAL_KSWAPD = 125;
+  VMSTAT_SWAP_RA = 126;
+  VMSTAT_SWAP_RA_HIT = 127;
+  VMSTAT_WORKINGSET_RESTORE = 128;
 }
\ No newline at end of file
diff --git a/protos/perfetto/config/BUILD.gn b/protos/perfetto/config/BUILD.gn
index 8a4a3b8..62f03ac 100644
--- a/protos/perfetto/config/BUILD.gn
+++ b/protos/perfetto/config/BUILD.gn
@@ -19,14 +19,13 @@
 # data_source_config.proto.
 
 perfetto_proto_library("@TYPE@") {
-  # Chromium shouldn't ignore the "android/" folder.
-  set_sources_assignment_filter([])
   deps = [
     "../common:@TYPE@",
     "android:@TYPE@",
     "ftrace:@TYPE@",
     "gpu:@TYPE@",
     "inode_file:@TYPE@",
+    "interceptors:@TYPE@",
     "power:@TYPE@",
     "process_stats:@TYPE@",
     "profiling:@TYPE@",
@@ -37,6 +36,8 @@
   sources = [
     "chrome/chrome_config.proto",
     "data_source_config.proto",
+    "interceptor_config.proto",
+    "stress_test_config.proto",
     "test_config.proto",
     "trace_config.proto",
   ]
@@ -48,3 +49,10 @@
   proto_generators = [ "lite" ]
   sources = [ "perfetto_config.proto" ]
 }
+
+perfetto_proto_library("descriptor") {
+  proto_generators = [ "descriptor" ]
+  generate_descriptor = "config.descriptor"
+  deps = [ ":source_set" ]
+  sources = [ "trace_config.proto" ]
+}
diff --git a/protos/perfetto/config/chrome/chrome_config.proto b/protos/perfetto/config/chrome/chrome_config.proto
index aa24c38..aa9333b 100644
--- a/protos/perfetto/config/chrome/chrome_config.proto
+++ b/protos/perfetto/config/chrome/chrome_config.proto
@@ -24,4 +24,25 @@
   // When enabled, the data source should only fill in fields in the output that
   // are not potentially privacy sensitive.
   optional bool privacy_filtering_enabled = 2;
+
+  // Instead of emitting binary protobuf, convert the trace data to the legacy
+  // JSON format. Note that the trace data will still be returned as a series of
+  // TracePackets, but the embedded data will be JSON instead of serialized
+  // protobuf.
+  optional bool convert_to_legacy_json = 3;
+
+  // Priority of the tracing session client. A higher priority session may
+  // preempt a lower priority one in configurations where concurrent sessions
+  // aren't supported.
+  enum ClientPriority {
+    UNKNOWN = 0;
+    BACKGROUND = 1;
+    USER_INITIATED = 2;
+  }
+  optional ClientPriority client_priority = 4;
+
+  // Applicable only when using legacy JSON format.
+  // If |json_agent_label_filter| is not empty, only data pertaining to
+  // the specified tracing agent label (e.g. "traceEvents") will be returned.
+  optional string json_agent_label_filter = 5;
 }
diff --git a/protos/perfetto/config/data_source_config.proto b/protos/perfetto/config/data_source_config.proto
index 6cac6ed..bdb0411 100644
--- a/protos/perfetto/config/data_source_config.proto
+++ b/protos/perfetto/config/data_source_config.proto
@@ -26,6 +26,7 @@
 import "protos/perfetto/config/gpu/gpu_counter_config.proto";
 import "protos/perfetto/config/gpu/vulkan_memory_config.proto";
 import "protos/perfetto/config/inode_file/inode_file_config.proto";
+import "protos/perfetto/config/interceptor_config.proto";
 import "protos/perfetto/config/power/android_power_config.proto";
 import "protos/perfetto/config/process_stats/process_stats_config.proto";
 import "protos/perfetto/config/profiling/heapprofd_config.proto";
@@ -36,6 +37,7 @@
 import "protos/perfetto/config/track_event/track_event_config.proto";
 
 // The configuration that is passed to each data source when starting tracing.
+// Next id: 116
 message DataSourceConfig {
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
@@ -68,6 +70,7 @@
   // one of which produces metadata for the other one, belong to the same trace
   // session and hence should be linked together.
   // This field was introduced in Aug 2018 after Android P.
+  // DO NOT SET in consumer as this will be overridden by the service.
   optional uint64 tracing_session_id = 4;
 
   // Keeep the lower IDs (up to 99) for fields that are *not* specific to
@@ -116,6 +119,16 @@
   // C++ class for it so it can pass around plain C++ objets.
   optional ChromeConfig chrome_config = 101;
 
+  // If an interceptor is specified here, packets for this data source will be
+  // rerouted to the interceptor instead of the main trace buffer. This can be
+  // used, for example, to write trace data into ETW or for logging trace points
+  // to the console.
+  //
+  // Note that interceptors are only supported by data sources registered
+  // through the Perfetto SDK API. Data sources that don't use that API (e.g.,
+  // traced_probes) may not support interception.
+  optional InterceptorConfig interceptor_config = 115;
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
diff --git a/protos/perfetto/config/ftrace/ftrace_config.proto b/protos/perfetto/config/ftrace/ftrace_config.proto
index 3b78baa..070b481 100644
--- a/protos/perfetto/config/ftrace/ftrace_config.proto
+++ b/protos/perfetto/config/ftrace/ftrace_config.proto
@@ -35,4 +35,17 @@
     optional bool enabled = 1;
   }
   optional CompactSchedConfig compact_sched = 12;
+
+  // Enables symbol name resolution against /proc/kallsyms.
+  // It requires that either traced_probes is running as root or that
+  // kptr_restrict has been manually lowered.
+  // It does not disclose KASLR, symbol addresses are mangled.
+  optional bool symbolize_ksyms = 13;
+
+  // By default the kernel symbolizer is lazily initialized on a deferred task
+  // to reduce ftrace's time-to-start-recording. Unfortunately that makes
+  // ksyms integration tests hard. This flag forces the kernel symbolizer to be
+  // initialized synchronously on the data source start and hence avoiding
+  // timing races in tests.
+  optional bool initialize_ksyms_synchronously_for_testing = 14;
 }
diff --git a/protos/perfetto/metrics/android/unmapped_java_symbols.proto b/protos/perfetto/config/interceptor_config.proto
similarity index 61%
rename from protos/perfetto/metrics/android/unmapped_java_symbols.proto
rename to protos/perfetto/config/interceptor_config.proto
index b14cb5d..7ecc13b 100644
--- a/protos/perfetto/metrics/android/unmapped_java_symbols.proto
+++ b/protos/perfetto/config/interceptor_config.proto
@@ -18,21 +18,14 @@
 
 package perfetto.protos;
 
-import "protos/perfetto/metrics/android/process_metadata.proto";
+import "protos/perfetto/config/interceptors/console_config.proto";
 
-message UnmappedJavaSymbols {
-  message Field {
-    optional string field_name = 1;
-    optional string field_type_name = 2;
-  }
+// Configuration for trace packet interception. Used for diverting trace data to
+// non-Perfetto sources (e.g., logging to the console, ETW) when using the
+// Perfetto SDK.
+message InterceptorConfig {
+  // Matches the name given to RegisterInterceptor().
+  optional string name = 1;
 
-  message ProcessSymbols {
-    reserved 3;
-
-    optional AndroidProcessMetadata process_metadata = 1;
-    repeated string type_name = 2;
-    repeated Field field = 4;
-  }
-
-  repeated ProcessSymbols process_symbols = 1;
+  optional ConsoleConfig console_config = 100 [lazy = true];
 }
diff --git a/protos/perfetto/config/interceptors/BUILD.gn b/protos/perfetto/config/interceptors/BUILD.gn
new file mode 100644
index 0000000..2330b10
--- /dev/null
+++ b/protos/perfetto/config/interceptors/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+import("../../../../gn/perfetto.gni")
+import("../../../../gn/proto_library.gni")
+
+perfetto_proto_library("@TYPE@") {
+  deps = [ "../../common:@TYPE@" ]
+  sources = [ "console_config.proto" ]
+}
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/protos/perfetto/config/interceptors/console_config.proto
similarity index 69%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to protos/perfetto/config/interceptors/console_config.proto
index e22189e..7f0189f 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/protos/perfetto/config/interceptors/console_config.proto
@@ -15,13 +15,14 @@
  */
 
 syntax = "proto2";
-
 package perfetto.protos;
 
-import "protos/perfetto/metrics/custom_options.proto";
-
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+message ConsoleConfig {
+  enum Output {
+    OUTPUT_UNSPECIFIED = 0;
+    OUTPUT_STDOUT = 1;
+    OUTPUT_STDERR = 2;
+  }
+  optional Output output = 1;
+  optional bool enable_colors = 2;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 5d05956..fcaeaff 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -323,6 +323,27 @@
   // When enabled, the data source should only fill in fields in the output that
   // are not potentially privacy sensitive.
   optional bool privacy_filtering_enabled = 2;
+
+  // Instead of emitting binary protobuf, convert the trace data to the legacy
+  // JSON format. Note that the trace data will still be returned as a series of
+  // TracePackets, but the embedded data will be JSON instead of serialized
+  // protobuf.
+  optional bool convert_to_legacy_json = 3;
+
+  // Priority of the tracing session client. A higher priority session may
+  // preempt a lower priority one in configurations where concurrent sessions
+  // aren't supported.
+  enum ClientPriority {
+    UNKNOWN = 0;
+    BACKGROUND = 1;
+    USER_INITIATED = 2;
+  }
+  optional ClientPriority client_priority = 4;
+
+  // Applicable only when using legacy JSON format.
+  // If |json_agent_label_filter| is not empty, only data pertaining to
+  // the specified tracing agent label (e.g. "traceEvents") will be returned.
+  optional string json_agent_label_filter = 5;
 }
 
 // End of protos/perfetto/config/chrome/chrome_config.proto
@@ -346,6 +367,19 @@
     optional bool enabled = 1;
   }
   optional CompactSchedConfig compact_sched = 12;
+
+  // Enables symbol name resolution against /proc/kallsyms.
+  // It requires that either traced_probes is running as root or that
+  // kptr_restrict has been manually lowered.
+  // It does not disclose KASLR, symbol addresses are mangled.
+  optional bool symbolize_ksyms = 13;
+
+  // By default the kernel symbolizer is lazily initialized on a deferred task
+  // to reduce ftrace's time-to-start-recording. Unfortunately that makes
+  // ksyms integration tests hard. This flag forces the kernel symbolizer to be
+  // initialized synchronously on the data source start and hence avoiding
+  // timing races in tests.
+  optional bool initialize_ksyms_synchronously_for_testing = 14;
 }
 
 // End of protos/perfetto/config/ftrace/ftrace_config.proto
@@ -412,6 +446,34 @@
 
 // End of protos/perfetto/config/inode_file/inode_file_config.proto
 
+// Begin of protos/perfetto/config/interceptors/console_config.proto
+
+message ConsoleConfig {
+  enum Output {
+    OUTPUT_UNSPECIFIED = 0;
+    OUTPUT_STDOUT = 1;
+    OUTPUT_STDERR = 2;
+  }
+  optional Output output = 1;
+  optional bool enable_colors = 2;
+}
+
+// End of protos/perfetto/config/interceptors/console_config.proto
+
+// Begin of protos/perfetto/config/interceptor_config.proto
+
+// Configuration for trace packet interception. Used for diverting trace data to
+// non-Perfetto sources (e.g., logging to the console, ETW) when using the
+// Perfetto SDK.
+message InterceptorConfig {
+  // Matches the name given to RegisterInterceptor().
+  optional string name = 1;
+
+  optional ConsoleConfig console_config = 100 [lazy = true];
+}
+
+// End of protos/perfetto/config/interceptor_config.proto
+
 // Begin of protos/perfetto/config/power/android_power_config.proto
 
 message AndroidPowerConfig {
@@ -493,7 +555,7 @@
 // Begin of protos/perfetto/config/profiling/heapprofd_config.proto
 
 // Configuration for go/heapprofd.
-// Next id: 21
+// Next id: 24
 message HeapprofdConfig {
   message ContinuousDumpConfig {
     // ms to wait before first dump.
@@ -502,12 +564,23 @@
     optional uint32 dump_interval_ms = 6;
   }
 
+  // Sampling rate for all heaps not specified via heap_sampling_intervals.
+  //
+  // These are:
+  // * All heaps if heap_sampling_intervals is empty.
+  // * Those profiled due to all_heaps and not named in heaps if
+  //   heap_sampling_intervals is not empty.
+  // * The implicit libc.malloc heap if heaps is empty.
+  //
   // Set to 1 for perfect accuracy.
   // Otherwise, sample every sample_interval_bytes on average.
   //
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#sampling-interval
   // for more details.
+  //
+  // BUGS
+  // Before Android 12, setting this to 0 would crash the target process.
   optional uint64 sampling_interval_bytes = 1;
 
   // E.g. surfaceflinger, com.android.phone
@@ -522,10 +595,29 @@
   // For watermark based triggering or local debugging.
   repeated uint64 pid = 4;
 
-  // Which heaps to sample, e.g. "malloc". If left empty, only samples
+  // Which heaps to sample, e.g. "libc.malloc". If left empty, only samples
   // "malloc".
+  //
+  // Introduced in Android 12.
   repeated string heaps = 20;
 
+  optional bool stream_allocations = 23;
+
+  // If given, needs to be the same length as heaps and gives the sampling
+  // interval for the respective entry in heaps.
+  //
+  // Otherwise, sampling_interval_bytes is used.
+  //
+  // It is recommended to set sampling_interval_bytes to a reasonable default
+  // value when using this, as a value of 0 for sampling_interval_bytes will
+  // crash the target process before Android 12.
+  //
+  // Introduced in Android 12.
+  repeated uint64 heap_sampling_intervals = 22;
+
+  // Sample all heaps registered by target process. Introduced in Android 12.
+  optional bool all_heaps = 21;
+
   // Profile all processes eligible for profiling on the system.
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#heapprofd-targets
@@ -592,17 +684,8 @@
   // Introduced in Android 11.
   optional bool no_running = 11;
 
-  // Gather information on how many bytes of allocations are on non-referenced
-  // pages. The way to use this generally is:
-  // 1. Start profile of app.
-  // 2. Start app.
-  // 3. Trigger a dump by sending SIGUSR1 to heapprofd.
-  // 4. Do operations.
-  // 5. End profile.
-  //
-  // You can then find the allocations that were not used for the operations you
-  // did in step 4.
-  optional bool idle_allocations = 12;
+  // deprecated idle_allocations.
+  reserved 12;
 
   // Cause heapprofd to emit a single dump at the end, showing the memory usage
   // at the point in time when the sampled heap usage of the process was at its
@@ -665,6 +748,10 @@
   // * start with /data/app
   // * contain "extracted in memory from Y", where Y matches any of the above
   optional bool dump_smaps = 5;
+
+  // Exclude objects of the following types from the profile. This can be
+  // useful if lots of uninteresting objects, e.g. "sun.misc.Cleaner".
+  repeated string ignored_types = 6;
 }
 
 // End of protos/perfetto/config/profiling/java_hprof_config.proto
@@ -675,6 +762,8 @@
 //
 // At the time of writing, the config options are restricted to the periodic
 // system-wide stack sampling use-case (|all_cpus| must be true).
+//
+// Next id: 14
 message PerfEventConfig {
   // If true, sample events on all CPUs.
   optional bool all_cpus = 1;
@@ -693,27 +782,52 @@
   // If unset, an implementation-defined default is used.
   optional uint32 ring_buffer_pages = 3;
 
-  // Process ID (TGID) whitelist. If this list is not empty, only matching
-  // samples will be retained. If multiple whitelists and blacklists are
+  // If true, callstacks will include the kernel-space frames. Such frames can
+  // be identified by a magical "kernel" string as their mapping name.
+  // Requires traced_perf to be running as root, or kptr_restrict to have been
+  // manually unrestricted.
+  // This does *not* disclose KASLR, as only the function names are emitted.
+  optional bool kernel_frames = 12;
+
+  //
+  // Target process selection:
+  //
+
+  // Process ID (TGID) allowlist. If this list is not empty, only matching
+  // samples will be retained. If multiple allow/deny-lists are
   // specified by the config, then all of them are evaluated for each sampled
   // process.
   repeated int32 target_pid = 4;
 
-  // Command line whitelist, matched against the
+  // Command line allowlist, matched against the
   // /proc/<pid>/cmdline (not the comm string), with both sides being
   // "normalized". Normalization is as follows: (1) trim everything beyond the
   // first null or "@" byte; (2) if the string contains forward slashes, trim
   // everything up to and including the last one.
   repeated string target_cmdline = 5;
 
-  // PID blacklist.
+  // List of excluded pids.
   repeated int32 exclude_pid = 6;
 
-  // Command line blacklist. Normalized in the same way as |target_cmdline|.
+  // List of excluded cmdlines. Normalized in the same way as |target_cmdline|.
   repeated string exclude_cmdline = 7;
 
-  ////////////////////
+  // Number of additional command lines to sample. Only those which are neither
+  // explicitly included nor excluded will be considered. Processes are accepted
+  // on a first come, first served basis.
+  optional uint32 additional_cmdline_count = 11;
+
+  //
+  // Daemon's resource usage limits:
+  //
+
+  // Stop the data source if traced_perf's combined {RssAnon + Swap} memory
+  // footprint exceeds this value.
+  optional uint32 max_daemon_memory_kb = 13;
+
+  //
   // Uncommon options:
+  //
 
   // Timeout for the remote /proc/<pid>/{maps,mem} file descriptors for a
   // sampled process. This is primarily for Android, where this lookup is
@@ -875,6 +989,39 @@
   VMSTAT_NR_ZSPAGES = 93;
   VMSTAT_NR_ION_HEAP = 94;
   VMSTAT_NR_GPU_HEAP = 95;
+  VMSTAT_ALLOCSTALL_DMA = 96;
+  VMSTAT_ALLOCSTALL_MOVABLE = 97;
+  VMSTAT_ALLOCSTALL_NORMAL = 98;
+  VMSTAT_COMPACT_DAEMON_FREE_SCANNED = 99;
+  VMSTAT_COMPACT_DAEMON_MIGRATE_SCANNED = 100;
+  VMSTAT_NR_FASTRPC = 101;
+  VMSTAT_NR_INDIRECTLY_RECLAIMABLE = 102;
+  VMSTAT_NR_ION_HEAP_POOL = 103;
+  VMSTAT_NR_KERNEL_MISC_RECLAIMABLE = 104;
+  VMSTAT_NR_SHADOW_CALL_STACK_BYTES = 105;
+  VMSTAT_NR_SHMEM_HUGEPAGES = 106;
+  VMSTAT_NR_SHMEM_PMDMAPPED = 107;
+  VMSTAT_NR_UNRECLAIMABLE_PAGES = 108;
+  VMSTAT_NR_ZONE_ACTIVE_ANON = 109;
+  VMSTAT_NR_ZONE_ACTIVE_FILE = 110;
+  VMSTAT_NR_ZONE_INACTIVE_ANON = 111;
+  VMSTAT_NR_ZONE_INACTIVE_FILE = 112;
+  VMSTAT_NR_ZONE_UNEVICTABLE = 113;
+  VMSTAT_NR_ZONE_WRITE_PENDING = 114;
+  VMSTAT_OOM_KILL = 115;
+  VMSTAT_PGLAZYFREE = 116;
+  VMSTAT_PGLAZYFREED = 117;
+  VMSTAT_PGREFILL = 118;
+  VMSTAT_PGSCAN_DIRECT = 119;
+  VMSTAT_PGSCAN_KSWAPD = 120;
+  VMSTAT_PGSKIP_DMA = 121;
+  VMSTAT_PGSKIP_MOVABLE = 122;
+  VMSTAT_PGSKIP_NORMAL = 123;
+  VMSTAT_PGSTEAL_DIRECT = 124;
+  VMSTAT_PGSTEAL_KSWAPD = 125;
+  VMSTAT_SWAP_RA = 126;
+  VMSTAT_SWAP_RA_HIT = 127;
+  VMSTAT_WORKINGSET_RESTORE = 128;
 }
 // End of protos/perfetto/common/sys_stats_counters.proto
 
@@ -1024,6 +1171,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
+// Next id: 116
 message DataSourceConfig {
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
@@ -1056,6 +1204,7 @@
   // one of which produces metadata for the other one, belong to the same trace
   // session and hence should be linked together.
   // This field was introduced in Aug 2018 after Android P.
+  // DO NOT SET in consumer as this will be overridden by the service.
   optional uint64 tracing_session_id = 4;
 
   // Keeep the lower IDs (up to 99) for fields that are *not* specific to
@@ -1104,6 +1253,16 @@
   // C++ class for it so it can pass around plain C++ objets.
   optional ChromeConfig chrome_config = 101;
 
+  // If an interceptor is specified here, packets for this data source will be
+  // rerouted to the interceptor instead of the main trace buffer. This can be
+  // used, for example, to write trace data into ETW or for logging trace points
+  // to the console.
+  //
+  // Note that interceptors are only supported by data sources registered
+  // through the Perfetto SDK API. Data sources that don't use that API (e.g.,
+  // traced_probes) may not support interception.
+  optional InterceptorConfig interceptor_config = 115;
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -1129,7 +1288,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 30.
+// Next id: 31.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -1327,6 +1486,15 @@
   // trace ends to notify it about the trace readiness.
   optional bool notify_traceur = 16;
 
+  // Android-only. If set to a value > 0, marks the trace session as a candidate
+  // for being attached to a bugreport. This field effectively acts as a z-index
+  // for bugreports. When Android's dumpstate runs perfetto
+  // --save-for-bugreport, traced will pick the tracing session with the highest
+  // score (score <= 0 is ignored), will steal its contents, save the trace into
+  // a known path and stop prematurely.
+  // This field was introduced in Android S.
+  optional int32 bugreport_score = 30;
+
   // Triggers allow producers to start or stop the tracing session when an event
   // occurs.
   //
diff --git a/protos/perfetto/config/profiling/heapprofd_config.proto b/protos/perfetto/config/profiling/heapprofd_config.proto
index 33c6609..0e007ac 100644
--- a/protos/perfetto/config/profiling/heapprofd_config.proto
+++ b/protos/perfetto/config/profiling/heapprofd_config.proto
@@ -19,7 +19,7 @@
 package perfetto.protos;
 
 // Configuration for go/heapprofd.
-// Next id: 21
+// Next id: 24
 message HeapprofdConfig {
   message ContinuousDumpConfig {
     // ms to wait before first dump.
@@ -28,12 +28,23 @@
     optional uint32 dump_interval_ms = 6;
   }
 
+  // Sampling rate for all heaps not specified via heap_sampling_intervals.
+  //
+  // These are:
+  // * All heaps if heap_sampling_intervals is empty.
+  // * Those profiled due to all_heaps and not named in heaps if
+  //   heap_sampling_intervals is not empty.
+  // * The implicit libc.malloc heap if heaps is empty.
+  //
   // Set to 1 for perfect accuracy.
   // Otherwise, sample every sample_interval_bytes on average.
   //
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#sampling-interval
   // for more details.
+  //
+  // BUGS
+  // Before Android 12, setting this to 0 would crash the target process.
   optional uint64 sampling_interval_bytes = 1;
 
   // E.g. surfaceflinger, com.android.phone
@@ -48,10 +59,29 @@
   // For watermark based triggering or local debugging.
   repeated uint64 pid = 4;
 
-  // Which heaps to sample, e.g. "malloc". If left empty, only samples
+  // Which heaps to sample, e.g. "libc.malloc". If left empty, only samples
   // "malloc".
+  //
+  // Introduced in Android 12.
   repeated string heaps = 20;
 
+  optional bool stream_allocations = 23;
+
+  // If given, needs to be the same length as heaps and gives the sampling
+  // interval for the respective entry in heaps.
+  //
+  // Otherwise, sampling_interval_bytes is used.
+  //
+  // It is recommended to set sampling_interval_bytes to a reasonable default
+  // value when using this, as a value of 0 for sampling_interval_bytes will
+  // crash the target process before Android 12.
+  //
+  // Introduced in Android 12.
+  repeated uint64 heap_sampling_intervals = 22;
+
+  // Sample all heaps registered by target process. Introduced in Android 12.
+  optional bool all_heaps = 21;
+
   // Profile all processes eligible for profiling on the system.
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#heapprofd-targets
@@ -118,17 +148,8 @@
   // Introduced in Android 11.
   optional bool no_running = 11;
 
-  // Gather information on how many bytes of allocations are on non-referenced
-  // pages. The way to use this generally is:
-  // 1. Start profile of app.
-  // 2. Start app.
-  // 3. Trigger a dump by sending SIGUSR1 to heapprofd.
-  // 4. Do operations.
-  // 5. End profile.
-  //
-  // You can then find the allocations that were not used for the operations you
-  // did in step 4.
-  optional bool idle_allocations = 12;
+  // deprecated idle_allocations.
+  reserved 12;
 
   // Cause heapprofd to emit a single dump at the end, showing the memory usage
   // at the point in time when the sampled heap usage of the process was at its
diff --git a/protos/perfetto/config/profiling/java_hprof_config.proto b/protos/perfetto/config/profiling/java_hprof_config.proto
index 7a6c652..5eb9118 100644
--- a/protos/perfetto/config/profiling/java_hprof_config.proto
+++ b/protos/perfetto/config/profiling/java_hprof_config.proto
@@ -53,4 +53,8 @@
   // * start with /data/app
   // * contain "extracted in memory from Y", where Y matches any of the above
   optional bool dump_smaps = 5;
+
+  // Exclude objects of the following types from the profile. This can be
+  // useful if lots of uninteresting objects, e.g. "sun.misc.Cleaner".
+  repeated string ignored_types = 6;
 }
diff --git a/protos/perfetto/config/profiling/perf_event_config.proto b/protos/perfetto/config/profiling/perf_event_config.proto
index f5b186c..b49c6e2 100644
--- a/protos/perfetto/config/profiling/perf_event_config.proto
+++ b/protos/perfetto/config/profiling/perf_event_config.proto
@@ -22,6 +22,8 @@
 //
 // At the time of writing, the config options are restricted to the periodic
 // system-wide stack sampling use-case (|all_cpus| must be true).
+//
+// Next id: 14
 message PerfEventConfig {
   // If true, sample events on all CPUs.
   optional bool all_cpus = 1;
@@ -40,27 +42,52 @@
   // If unset, an implementation-defined default is used.
   optional uint32 ring_buffer_pages = 3;
 
-  // Process ID (TGID) whitelist. If this list is not empty, only matching
-  // samples will be retained. If multiple whitelists and blacklists are
+  // If true, callstacks will include the kernel-space frames. Such frames can
+  // be identified by a magical "kernel" string as their mapping name.
+  // Requires traced_perf to be running as root, or kptr_restrict to have been
+  // manually unrestricted.
+  // This does *not* disclose KASLR, as only the function names are emitted.
+  optional bool kernel_frames = 12;
+
+  //
+  // Target process selection:
+  //
+
+  // Process ID (TGID) allowlist. If this list is not empty, only matching
+  // samples will be retained. If multiple allow/deny-lists are
   // specified by the config, then all of them are evaluated for each sampled
   // process.
   repeated int32 target_pid = 4;
 
-  // Command line whitelist, matched against the
+  // Command line allowlist, matched against the
   // /proc/<pid>/cmdline (not the comm string), with both sides being
   // "normalized". Normalization is as follows: (1) trim everything beyond the
   // first null or "@" byte; (2) if the string contains forward slashes, trim
   // everything up to and including the last one.
   repeated string target_cmdline = 5;
 
-  // PID blacklist.
+  // List of excluded pids.
   repeated int32 exclude_pid = 6;
 
-  // Command line blacklist. Normalized in the same way as |target_cmdline|.
+  // List of excluded cmdlines. Normalized in the same way as |target_cmdline|.
   repeated string exclude_cmdline = 7;
 
-  ////////////////////
+  // Number of additional command lines to sample. Only those which are neither
+  // explicitly included nor excluded will be considered. Processes are accepted
+  // on a first come, first served basis.
+  optional uint32 additional_cmdline_count = 11;
+
+  //
+  // Daemon's resource usage limits:
+  //
+
+  // Stop the data source if traced_perf's combined {RssAnon + Swap} memory
+  // footprint exceeds this value.
+  optional uint32 max_daemon_memory_kb = 13;
+
+  //
   // Uncommon options:
+  //
 
   // Timeout for the remote /proc/<pid>/{maps,mem} file descriptors for a
   // sampled process. This is primarily for Android, where this lookup is
diff --git a/protos/perfetto/config/stress_test_config.proto b/protos/perfetto/config/stress_test_config.proto
new file mode 100644
index 0000000..6ed8cbc
--- /dev/null
+++ b/protos/perfetto/config/stress_test_config.proto
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+import "protos/perfetto/config/trace_config.proto";
+
+package perfetto.protos;
+
+// This is the schema for the config files in /test/stress_test/configs/*.cfg.
+message StressTestConfig {
+  optional TraceConfig trace_config = 1;
+
+  // Shared Memory Buffer setup, passed as arguments to Tracing.Initialize().
+  optional uint32 shmem_size_kb = 2;
+  optional uint32 shmem_page_size_kb = 3;
+
+  // How many producer processes to spawn.
+  optional uint32 num_processes = 4;
+
+  // How many writer threads each producer process should spawn.
+  optional uint32 num_threads = 5;
+
+  // The producer will write events until one of the following is met:
+  // - trace_config.duration_ms is reached.
+  // - max_events is reached.
+  optional uint32 max_events = 6;
+
+  // If > 0 will write nested messages up to N levels deep. The size of each
+  // nested message depends on the payload_mean / sttdev arguments (below).
+  // This is to cover the patching logic.
+  optional uint32 nesting = 7;
+
+  // This submessage defines the timings of each writer worker thread.
+  message WriterTiming {
+    // The size of the payload written on each iteration.
+    optional double payload_mean = 1;
+    optional double payload_stddev = 2;
+
+    // The nominal event writing rate, expressed in events/sec.
+    // E.g. if payload_mean = 500 (bytes) and rate_mean = 1000 (Hz), each thread
+    // will write 500 KB / sec approximately (% stalling).
+    optional double rate_mean = 3;
+    optional double rate_stddev = 4;
+
+    // If non-zero each worker will slow down the writing of the payload:
+    // it writes half payload, sleep for payload_write_time_ms, then write the
+    // other half.
+    optional uint32 payload_write_time_ms = 5;
+  }
+
+  // The timings used by default.
+  optional WriterTiming steady_state_timings = 8;
+
+  // Optionally it is possible to cause a writer to enter "burst mode",
+  // simulating peaks of high-intensity writing. The way it works is the
+  // following: by default the writer writes events using the
+  // |steady_state_timings|. Then every |burst_period_ms| it will switch to the
+  // |burst_timings| for |burst_duration_ms|, and go back to the steady state
+  // after that (and then repeat).
+  optional uint32 burst_period_ms = 9;
+  optional uint32 burst_duration_ms = 10;
+  optional WriterTiming burst_timings = 11;
+}
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index 43e1e96..1559933 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: 30.
+// Next id: 31.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -224,6 +224,15 @@
   // trace ends to notify it about the trace readiness.
   optional bool notify_traceur = 16;
 
+  // Android-only. If set to a value > 0, marks the trace session as a candidate
+  // for being attached to a bugreport. This field effectively acts as a z-index
+  // for bugreports. When Android's dumpstate runs perfetto
+  // --save-for-bugreport, traced will pick the tracing session with the highest
+  // score (score <= 0 is ignored), will steal its contents, save the trace into
+  // a known path and stop prematurely.
+  // This field was introduced in Android S.
+  optional int32 bugreport_score = 30;
+
   // Triggers allow producers to start or stop the tracing session when an event
   // occurs.
   //
diff --git a/protos/perfetto/ipc/consumer_port.proto b/protos/perfetto/ipc/consumer_port.proto
index b8b929c..41242e7 100644
--- a/protos/perfetto/ipc/consumer_port.proto
+++ b/protos/perfetto/ipc/consumer_port.proto
@@ -112,6 +112,12 @@
   // backward/forward compatibility and feature detection.
   rpc QueryCapabilities(QueryCapabilitiesRequest)
       returns (QueryCapabilitiesResponse) {}
+
+  // ----------------------------------------------------
+  // All methods below have been introduced in Android S.
+  // ----------------------------------------------------
+  rpc SaveTraceForBugreport(SaveTraceForBugreportRequest)
+      returns (SaveTraceForBugreportResponse) {}
 }
 
 // Arguments for rpc EnableTracing().
@@ -127,6 +133,10 @@
 
 message EnableTracingResponse {
   oneof state { bool disabled = 1; }
+
+  // If present and non-empty tracing was disabled because of an error.
+  // Introduced in Android S.
+  optional string error = 3;
 }
 
 // Arguments for rpc StartTracing().
@@ -242,4 +252,18 @@
 
 message QueryCapabilitiesResponse {
   optional TracingServiceCapabilities capabilities = 1;
-}
\ No newline at end of file
+}
+
+// Arguments for rpc SaveTraceForBugreport.
+message SaveTraceForBugreportRequest {}
+
+// This response is sent only after the trace was saved into file (if succeeded)
+// or something failed.
+message SaveTraceForBugreportResponse {
+  // If true, an eligible the trace was saved into a known location (on Android
+  // /data/misc/perfetto-traces, see kBugreportTracePath).
+  // If false no trace with bugreport_score > 0 was found or an error occurred.
+  // see |msg| in that case for details about the failure.
+  optional bool success = 1;
+  optional string msg = 2;
+}
diff --git a/protos/perfetto/ipc/producer_port.proto b/protos/perfetto/ipc/producer_port.proto
index 76eb0ef..1b193eb 100644
--- a/protos/perfetto/ipc/producer_port.proto
+++ b/protos/perfetto/ipc/producer_port.proto
@@ -159,6 +159,11 @@
   // InitializeConnectionRequest (if any). If false, the shared memory buffer FD
   // will provided by the service via the SetupTracing async command.
   optional bool using_shmem_provided_by_producer = 1;
+
+  // Indicates to the producer that the service allows direct SMB patching of
+  // chunks that have not yet been committed to it.
+  // This field has been introduced in Android S.
+  optional bool direct_smb_patching_supported = 2;
 }
 
 // Arguments for rpc RegisterDataSource().
diff --git a/protos/perfetto/metrics/BUILD.gn b/protos/perfetto/metrics/BUILD.gn
index 8a35ef1..a0aec35 100644
--- a/protos/perfetto/metrics/BUILD.gn
+++ b/protos/perfetto/metrics/BUILD.gn
@@ -16,14 +16,23 @@
 import("../../../gn/proto_library.gni")
 
 perfetto_proto_library("@TYPE@") {
+  proto_generators = [
+    "lite",
+    "source_set",
+  ]
   deps = [ "android:@TYPE@" ]
   sources = [ "metrics.proto" ]
 }
 
-if (perfetto_build_standalone) {
-  perfetto_proto_library("descriptor") {
-    proto_generators = [ "descriptor" ]
-    generate_descriptor = "metrics.descriptor"
-    sources = [ "metrics.proto" ]
-  }
+perfetto_proto_library("custom_options_@TYPE@") {
+  proto_generators = [ "source_set" ]
+  sources = [ "custom_options.proto" ]
+  import_dirs = [ "${perfetto_protobuf_src_dir}" ]
+}
+
+perfetto_proto_library("descriptor") {
+  proto_generators = [ "descriptor" ]
+  generate_descriptor = "metrics.descriptor"
+  deps = [ "android:source_set" ]
+  sources = [ "metrics.proto" ]
 }
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index eb5de1f..82eb6cb 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -15,10 +15,15 @@
 import("../../../../gn/proto_library.gni")
 
 perfetto_proto_library("@TYPE@") {
+  proto_generators = [
+    "lite",
+    "source_set",
+  ]
   sources = [
     "batt_metric.proto",
     "cpu_metric.proto",
     "display_metrics.proto",
+    "gpu_metric.proto",
     "heap_profile_callsites.proto",
     "hwui_metric.proto",
     "ion_metric.proto",
@@ -33,9 +38,9 @@
     "process_metadata.proto",
     "startup_metric.proto",
     "surfaceflinger.proto",
+    "sysui_cuj_metrics.proto",
     "task_names.proto",
     "thread_time_in_state_metric.proto",
-    "unmapped_java_symbols.proto",
     "unsymbolized_frames.proto",
   ]
 }
diff --git a/protos/perfetto/metrics/android/batt_metric.proto b/protos/perfetto/metrics/android/batt_metric.proto
index 0382fac..015a2a8 100644
--- a/protos/perfetto/metrics/android/batt_metric.proto
+++ b/protos/perfetto/metrics/android/batt_metric.proto
@@ -44,9 +44,17 @@
     optional int64 sleep_screen_doze_ns = 8;
   }
 
+  // Period of time during the trace that the device went to sleep completely.
+  message SuspendPeriod {
+    optional int64 timestamp_ns = 1;
+    optional int64 duration_ns = 2;
+  }
+
   // Battery counters info for each ts of the trace. This should only be
   // extracted for short traces.
   repeated BatteryCounters battery_counters = 1;
 
   optional BatteryAggregates battery_aggregates = 2;
+
+  repeated SuspendPeriod suspend_period = 3;
 }
diff --git a/protos/perfetto/metrics/android/display_metrics.proto b/protos/perfetto/metrics/android/display_metrics.proto
index fc858ac..4c36b5b 100644
--- a/protos/perfetto/metrics/android/display_metrics.proto
+++ b/protos/perfetto/metrics/android/display_metrics.proto
@@ -27,4 +27,7 @@
 
   // Stat reports whether there is any duplicate_frames tracked
   optional uint32 duplicate_frames_logged = 2;
-}
\ No newline at end of file
+
+  // Stat that reports the number of dpu underrrun occurs count.
+  optional uint32 total_dpu_underrun_count = 3;
+}
diff --git a/protos/perfetto/metrics/android/gpu_metric.proto b/protos/perfetto/metrics/android/gpu_metric.proto
new file mode 100644
index 0000000..2365855
--- /dev/null
+++ b/protos/perfetto/metrics/android/gpu_metric.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+message AndroidGpuMetric {
+  message Process {
+    // Process name.
+    optional string name = 1;
+
+    // max/min/avg GPU memory used by this process.
+    optional int64 mem_max = 2;
+    optional int64 mem_min = 3;
+    optional int64 mem_avg = 4;
+  }
+
+  // GPU metric for processes using GPU.
+  repeated Process processes = 1;
+
+  // max/min/avg GPU memory used by the entire system.
+  optional int64 mem_max = 2;
+  optional int64 mem_min = 3;
+  optional int64 mem_avg = 4;
+}
diff --git a/protos/perfetto/metrics/android/java_heap_histogram.proto b/protos/perfetto/metrics/android/java_heap_histogram.proto
index f0680a0..ce23669 100644
--- a/protos/perfetto/metrics/android/java_heap_histogram.proto
+++ b/protos/perfetto/metrics/android/java_heap_histogram.proto
@@ -19,8 +19,11 @@
 import "protos/perfetto/metrics/android/process_metadata.proto";
 
 message JavaHeapHistogram {
+  // Next id: 5
   message TypeCount {
     optional string type_name = 1;
+    optional string category = 4;
+
     optional uint32 obj_count = 2;
     optional uint32 reachable_obj_count = 3;
   }
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index ce7e9e3..5141044 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -38,7 +38,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 21.
+  // Next id: 22.
   message ToFirstFrame {
     optional int64 dur_ns = 1;
     optional double dur_ms = 17;
@@ -57,6 +57,7 @@
     optional Slice time_bind_application = 6;
     optional Slice time_activity_start = 7;
     optional Slice time_activity_resume = 8;
+    optional Slice time_activity_restart = 21;
     optional Slice time_choreographer = 9;
 
     // If we are starting a new process, record the duration from the
diff --git a/protos/perfetto/metrics/android/sysui_cuj_metrics.proto b/protos/perfetto/metrics/android/sysui_cuj_metrics.proto
new file mode 100644
index 0000000..589ca46
--- /dev/null
+++ b/protos/perfetto/metrics/android/sysui_cuj_metrics.proto
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Metric that stores frame information and potential jank root causes
+// for a single Android system UI interaction/user journey.
+message AndroidSysUiCujMetrics {
+  // A list of all frames within the SysUi user journey.
+  repeated Frame frames = 1;
+
+  message Frame {
+    // Index of the frame within the single user journey.
+    optional int64 number = 1;
+    optional int64 ts = 2;
+    optional int64 dur = 3;
+
+    // A list of identified potential causes for jank.
+    // Optional.
+    repeated string jank_cause = 4;
+  }
+}
diff --git a/protos/perfetto/metrics/chrome/BUILD.gn b/protos/perfetto/metrics/chrome/BUILD.gn
index 012a2c0..6287d8c 100644
--- a/protos/perfetto/metrics/chrome/BUILD.gn
+++ b/protos/perfetto/metrics/chrome/BUILD.gn
@@ -15,9 +15,21 @@
 import("../../../../gn/proto_library.gni")
 
 perfetto_proto_library("@TYPE@") {
-  deps = [ "../metrics:@TYPE@" ]
+  proto_generators = [ "source_set" ]
+  deps = [
+    "..:@TYPE@",
+    "..:custom_options_@TYPE@",
+  ]
   sources = [
     "all_chrome_metrics.proto",
-    "console_error_metric.proto",
+    "test_chrome_metric.proto",
   ]
 }
+
+perfetto_proto_library("descriptor") {
+  proto_generators = [ "descriptor" ]
+  import_dirs = [ "${perfetto_protobuf_src_dir}" ]
+  generate_descriptor = "all_chrome_metrics.descriptor"
+  deps = [ ":source_set" ]
+  sources = [ "all_chrome_metrics.proto" ]
+}
diff --git a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
index 1b2a874..6863170 100644
--- a/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
+++ b/protos/perfetto/metrics/chrome/all_chrome_metrics.proto
@@ -19,8 +19,8 @@
 package perfetto.protos;
 
 import "protos/perfetto/metrics/metrics.proto";
-import "protos/perfetto/metrics/chrome/console_error_metric.proto";
+import "protos/perfetto/metrics/chrome/test_chrome_metric.proto";
 
 extend TraceMetrics {
-  optional ConsoleErrorMetric console_error_metric = 1001;
+  optional TestChromeMetric test_chrome_metric = 1001;
 }
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/protos/perfetto/metrics/chrome/test_chrome_metric.proto
similarity index 75%
rename from protos/perfetto/metrics/chrome/console_error_metric.proto
rename to protos/perfetto/metrics/chrome/test_chrome_metric.proto
index e22189e..3ae3240 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/protos/perfetto/metrics/chrome/test_chrome_metric.proto
@@ -20,8 +20,6 @@
 
 import "protos/perfetto/metrics/custom_options.proto";
 
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+message TestChromeMetric {
+  optional int64 test_value = 1 [(unit) = "count_smallerIsBetter"];
 }
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 6384e76..dfa466d 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -21,6 +21,7 @@
 import "protos/perfetto/metrics/android/batt_metric.proto";
 import "protos/perfetto/metrics/android/cpu_metric.proto";
 import "protos/perfetto/metrics/android/display_metrics.proto";
+import "protos/perfetto/metrics/android/gpu_metric.proto";
 import "protos/perfetto/metrics/android/heap_profile_callsites.proto";
 import "protos/perfetto/metrics/android/hwui_metric.proto";
 import "protos/perfetto/metrics/android/ion_metric.proto";
@@ -34,9 +35,9 @@
 import "protos/perfetto/metrics/android/powrails_metric.proto";
 import "protos/perfetto/metrics/android/startup_metric.proto";
 import "protos/perfetto/metrics/android/surfaceflinger.proto";
+import "protos/perfetto/metrics/android/sysui_cuj_metrics.proto";
 import "protos/perfetto/metrics/android/task_names.proto";
 import "protos/perfetto/metrics/android/thread_time_in_state_metric.proto";
-import "protos/perfetto/metrics/android/unmapped_java_symbols.proto";
 import "protos/perfetto/metrics/android/unsymbolized_frames.proto";
 
 // Trace processor metadata
@@ -53,9 +54,9 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 26
+// Next id: 28
 message TraceMetrics {
-  reserved 4, 10, 13, 14;
+  reserved 4, 10, 13, 14, 19;
 
   // Battery counters metric on Android.
   optional AndroidBatteryMetric android_batt = 5;
@@ -105,9 +106,6 @@
   // Metrics used to find potential culprits of low-memory kills.
   optional AndroidLmkReasonMetric android_lmk_reason = 18;
 
-  // Java type names that have no deobfuscation mappings.
-  optional UnmappedJavaSymbols unmapped_java_symbols = 19;
-
   optional AndroidHwuiMetric android_hwui_metric = 20;
 
   optional AndroidDisplayMetrics display_metrics = 22;
@@ -119,6 +117,12 @@
   // Metric associated with surfaceflinger.
   optional AndroidSurfaceflingerMetric android_surfaceflinger = 25;
 
+  // GPU metrics on Android.
+  optional AndroidGpuMetric android_gpu = 26;
+
+  // Frame timing and jank root causes for system UI interactions.
+  optional AndroidSysUiCujMetrics android_sysui_cuj = 27;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 620a2d6..838587b 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -39,11 +39,19 @@
     optional int64 sleep_screen_doze_ns = 8;
   }
 
+  // Period of time during the trace that the device went to sleep completely.
+  message SuspendPeriod {
+    optional int64 timestamp_ns = 1;
+    optional int64 duration_ns = 2;
+  }
+
   // Battery counters info for each ts of the trace. This should only be
   // extracted for short traces.
   repeated BatteryCounters battery_counters = 1;
 
   optional BatteryAggregates battery_aggregates = 2;
+
+  repeated SuspendPeriod suspend_period = 3;
 }
 
 // End of protos/perfetto/metrics/android/batt_metric.proto
@@ -122,9 +130,37 @@
 
   // Stat reports whether there is any duplicate_frames tracked
   optional uint32 duplicate_frames_logged = 2;
+
+  // Stat that reports the number of dpu underrrun occurs count.
+  optional uint32 total_dpu_underrun_count = 3;
 }
+
 // End of protos/perfetto/metrics/android/display_metrics.proto
 
+// Begin of protos/perfetto/metrics/android/gpu_metric.proto
+
+message AndroidGpuMetric {
+  message Process {
+    // Process name.
+    optional string name = 1;
+
+    // max/min/avg GPU memory used by this process.
+    optional int64 mem_max = 2;
+    optional int64 mem_min = 3;
+    optional int64 mem_avg = 4;
+  }
+
+  // GPU metric for processes using GPU.
+  repeated Process processes = 1;
+
+  // max/min/avg GPU memory used by the entire system.
+  optional int64 mem_max = 2;
+  optional int64 mem_min = 3;
+  optional int64 mem_avg = 4;
+}
+
+// End of protos/perfetto/metrics/android/gpu_metric.proto
+
 // Begin of protos/perfetto/metrics/android/process_metadata.proto
 
 message AndroidProcessMetadata {
@@ -326,8 +362,11 @@
 // Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
 
 message JavaHeapHistogram {
+  // Next id: 5
   message TypeCount {
     optional string type_name = 1;
+    optional string category = 4;
+
     optional uint32 obj_count = 2;
     optional uint32 reachable_obj_count = 3;
   }
@@ -568,7 +607,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 21.
+  // Next id: 22.
   message ToFirstFrame {
     optional int64 dur_ns = 1;
     optional double dur_ms = 17;
@@ -587,6 +626,7 @@
     optional Slice time_bind_application = 6;
     optional Slice time_activity_start = 7;
     optional Slice time_activity_resume = 8;
+    optional Slice time_activity_restart = 21;
     optional Slice time_choreographer = 9;
 
     // If we are starting a new process, record the duration from the
@@ -665,6 +705,28 @@
 
 // End of protos/perfetto/metrics/android/surfaceflinger.proto
 
+// Begin of protos/perfetto/metrics/android/sysui_cuj_metrics.proto
+
+// Metric that stores frame information and potential jank root causes
+// for a single Android system UI interaction/user journey.
+message AndroidSysUiCujMetrics {
+  // A list of all frames within the SysUi user journey.
+  repeated Frame frames = 1;
+
+  message Frame {
+    // Index of the frame within the single user journey.
+    optional int64 number = 1;
+    optional int64 ts = 2;
+    optional int64 dur = 3;
+
+    // A list of identified potential causes for jank.
+    // Optional.
+    repeated string jank_cause = 4;
+  }
+}
+
+// End of protos/perfetto/metrics/android/sysui_cuj_metrics.proto
+
 // Begin of protos/perfetto/metrics/android/task_names.proto
 
 message AndroidTaskNames {
@@ -718,27 +780,6 @@
 
 // End of protos/perfetto/metrics/android/thread_time_in_state_metric.proto
 
-// Begin of protos/perfetto/metrics/android/unmapped_java_symbols.proto
-
-message UnmappedJavaSymbols {
-  message Field {
-    optional string field_name = 1;
-    optional string field_type_name = 2;
-  }
-
-  message ProcessSymbols {
-    reserved 3;
-
-    optional AndroidProcessMetadata process_metadata = 1;
-    repeated string type_name = 2;
-    repeated Field field = 4;
-  }
-
-  repeated ProcessSymbols process_symbols = 1;
-}
-
-// End of protos/perfetto/metrics/android/unmapped_java_symbols.proto
-
 // Begin of protos/perfetto/metrics/android/unsymbolized_frames.proto
 
 message UnsymbolizedFrames {
@@ -769,9 +810,9 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 26
+// Next id: 28
 message TraceMetrics {
-  reserved 4, 10, 13, 14;
+  reserved 4, 10, 13, 14, 19;
 
   // Battery counters metric on Android.
   optional AndroidBatteryMetric android_batt = 5;
@@ -821,9 +862,6 @@
   // Metrics used to find potential culprits of low-memory kills.
   optional AndroidLmkReasonMetric android_lmk_reason = 18;
 
-  // Java type names that have no deobfuscation mappings.
-  optional UnmappedJavaSymbols unmapped_java_symbols = 19;
-
   optional AndroidHwuiMetric android_hwui_metric = 20;
 
   optional AndroidDisplayMetrics display_metrics = 22;
@@ -835,6 +873,12 @@
   // Metric associated with surfaceflinger.
   optional AndroidSurfaceflingerMetric android_surfaceflinger = 25;
 
+  // GPU metrics on Android.
+  optional AndroidGpuMetric android_gpu = 26;
+
+  // Frame timing and jank root causes for system UI interactions.
+  optional AndroidSysUiCujMetrics android_sysui_cuj = 27;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index 07ee972..0c39d63 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -32,6 +32,7 @@
   "trace_packet.proto",
   "trace.proto",
   "extension_descriptor.proto",
+  "memory_graph.proto",
 ]
 
 proto_sources_minimal = [
diff --git a/protos/perfetto/trace/android/BUILD.gn b/protos/perfetto/trace/android/BUILD.gn
index 2bece63..c957f64 100644
--- a/protos/perfetto/trace/android/BUILD.gn
+++ b/protos/perfetto/trace/android/BUILD.gn
@@ -19,6 +19,7 @@
 
   sources = [
     "android_log.proto",
+    "frame_timeline_event.proto",
     "gpu_mem_event.proto",
     "graphics_frame_event.proto",
     "initial_display_state.proto",
diff --git a/protos/perfetto/trace/android/frame_timeline_event.proto b/protos/perfetto/trace/android/frame_timeline_event.proto
new file mode 100644
index 0000000..65725f4
--- /dev/null
+++ b/protos/perfetto/trace/android/frame_timeline_event.proto
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package perfetto.protos;
+
+// Generated by SurfaceFlinger's FrameTimeline (go/adaptive-scheduling-fr).
+// Used in comparing the expected timeline of a frame to the actual timeline.
+// Key terms:
+//    1) DisplayFrame - represents SurfaceFlinger's work on a frame(composited)
+//    2) SurfaceFrame - represents App's work on its frame
+//    3) Timeline = start to end of a component's(app/SF) work on a frame.
+// SurfaceFlinger composites frames from many apps together, so
+//    One DisplayFrame can map to N SurfaceFrame(s)
+// This relationship can be reconstructed by using
+//    DisplayFrame.token = SurfaceFrame.display_frame_token
+message FrameTimelineEvent {
+  // Specifies which component was the main source for the jank.
+  enum JankType {
+    JANK_UNSPECIFIED = 0;
+    JANK_NONE = 1;
+    JANK_SF_SCHEDULING = 2;
+    JANK_PREDICTION_ERROR = 3;
+    JANK_DISPLAY_HAL = 4;
+    JANK_SF_DEADLINE_MISSED = 5;
+    JANK_APP_DEADLINE_MISSED = 6;
+    JANK_BUFFER_STUFFING = 7;
+    JANK_UNKNOWN = 8;
+  };
+
+  // Specifies how a frame was presented on screen w.r.t. timing.
+  // Can be different for SurfaceFrame and DisplayFrame.
+  enum PresentType {
+    PRESENT_UNSPECIFIED = 0;
+    PRESENT_ON_TIME = 1;
+    PRESENT_LATE = 2;
+    PRESENT_EARLY = 3;
+    PRESENT_DROPPED = 4;
+  };
+
+  // Represents the app's work on a frame.
+  // Next id: 13
+  message SurfaceFrame {
+    // Token received by the app for its work. Can be shared between multiple
+    // layers of the same app (example: pip mode).
+    optional int64 token = 1;
+
+    // The corresponding DisplayFrame token is required to link the App's work
+    // with SurfaceFlinger's work. Many SurfaceFrames can be mapped to a single
+    // DisplayFrame.
+    // this.display_frame_token = DisplayFrame.token
+    optional int64 display_frame_token = 12;
+
+    optional PresentType present_type = 2;
+    optional bool on_time_finish = 3;
+    optional bool gpu_composition = 4;
+    optional JankType jank_type = 5;
+
+    // Timestamps in nanoseconds using CLOCK_MONOTONIC.
+    // Expected timeline = expected_start to expected_end.
+    optional int64 expected_start_ns = 6;
+    optional int64 expected_end_ns = 7;
+
+    // (b/172587309) Apps currently do not provide actual start time.
+    // Actual timeline = expected_start to actual_end.
+    optional int64 actual_start_ns = 8;
+    // If two SufaceFrames have the same token and same pid, then
+    //  actual timeline =
+    //    expected_start to max(frame1.actual_end, frame2.actual_end)
+    optional int64 actual_end_ns = 9;
+
+    // Pid of the app. Used in creating the timeline tracks (and slices) inside
+    // the respective process track group.
+    optional int32 pid = 10;
+    optional string layer_name = 11;
+  };
+
+  // Represents the SurfaceFlinger's work on a frame.
+  message DisplayFrame {
+    // Token received by SurfaceFlinger for its work
+    // this.token = SurfaceFrame.display_frame_token
+    optional int64 token = 1;
+    optional PresentType present_type = 2;
+    optional bool on_time_finish = 3;
+    optional bool gpu_composition = 4;
+    optional JankType jank_type = 5;
+
+    // Timestamps in nanoseconds using CLOCK_MONOTONIC.
+    // Expected timeline = expected_start to expected_end.
+    optional int64 expected_start_ns = 6;
+    optional int64 expected_end_ns = 7;
+
+    // Actual timeline = actual_start to actual_end.
+    optional int64 actual_start_ns = 8;
+    optional int64 actual_end_ns = 9;
+
+    // Pid of SurfaceFlinger. Used in creating the timeline tracks (and slices)
+    // inside the SurfaceFlinger process group.
+    optional int32 pid = 10;
+  };
+
+  oneof event {
+    DisplayFrame display_frame = 1;
+    SurfaceFrame surface_frame = 2;
+  }
+}
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 5eb1823..6ae204f 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -25,11 +25,15 @@
   "cgroup.proto",
   "clk.proto",
   "compaction.proto",
+  "cpuhp.proto",
+  "dpu.proto",
   "ext4.proto",
   "f2fs.proto",
+  "fastrpc.proto",
   "fence.proto",
   "filemap.proto",
   "ftrace.proto",
+  "g2d.proto",
   "gpu_mem.proto",
   "i2c.proto",
   "ion.proto",
diff --git a/protos/perfetto/trace/ftrace/cpuhp.proto b/protos/perfetto/trace/ftrace/cpuhp.proto
new file mode 100644
index 0000000..3214b51
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/cpuhp.proto
@@ -0,0 +1,31 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message CpuhpExitFtraceEvent {
+  optional uint32 cpu = 1;
+  optional int32 idx = 2;
+  optional int32 ret = 3;
+  optional int32 state = 4;
+}
+message CpuhpMultiEnterFtraceEvent {
+  optional uint32 cpu = 1;
+  optional uint64 fun = 2;
+  optional int32 idx = 3;
+  optional int32 target = 4;
+}
+message CpuhpEnterFtraceEvent {
+  optional uint32 cpu = 1;
+  optional uint64 fun = 2;
+  optional int32 idx = 3;
+  optional int32 target = 4;
+}
+message CpuhpLatencyFtraceEvent {
+  optional uint32 cpu = 1;
+  optional int32 ret = 2;
+  optional uint32 state = 3;
+  optional uint64 time = 4;
+}
diff --git a/protos/perfetto/trace/ftrace/dpu.proto b/protos/perfetto/trace/ftrace/dpu.proto
new file mode 100644
index 0000000..31eefc3
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/dpu.proto
@@ -0,0 +1,15 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message DpuTracingMarkWriteFtraceEvent {
+  optional int32 pid = 1;
+  optional string trace_name = 2;
+  optional uint32 trace_begin = 3;
+  optional string name = 4;
+  optional uint32 type = 5;
+  optional int32 value = 6;
+}
diff --git a/protos/perfetto/trace/ftrace/fastrpc.proto b/protos/perfetto/trace/ftrace/fastrpc.proto
new file mode 100644
index 0000000..15d694c
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/fastrpc.proto
@@ -0,0 +1,12 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message FastrpcDmaStatFtraceEvent {
+  optional int32 cid = 1;
+  optional int64 len = 2;
+  optional uint64 total_allocated = 3;
+}
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 72b2f89..0a49ae2 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -25,11 +25,15 @@
 import "protos/perfetto/trace/ftrace/cgroup.proto";
 import "protos/perfetto/trace/ftrace/clk.proto";
 import "protos/perfetto/trace/ftrace/compaction.proto";
+import "protos/perfetto/trace/ftrace/cpuhp.proto";
+import "protos/perfetto/trace/ftrace/dpu.proto";
 import "protos/perfetto/trace/ftrace/ext4.proto";
 import "protos/perfetto/trace/ftrace/f2fs.proto";
+import "protos/perfetto/trace/ftrace/fastrpc.proto";
 import "protos/perfetto/trace/ftrace/fence.proto";
 import "protos/perfetto/trace/ftrace/filemap.proto";
 import "protos/perfetto/trace/ftrace/ftrace.proto";
+import "protos/perfetto/trace/ftrace/g2d.proto";
 import "protos/perfetto/trace/ftrace/gpu_mem.proto";
 import "protos/perfetto/trace/ftrace/i2c.proto";
 import "protos/perfetto/trace/ftrace/ion.proto";
@@ -425,5 +429,12 @@
     GpuMemTotalFtraceEvent gpu_mem_total = 340;
     ThermalTemperatureFtraceEvent thermal_temperature = 341;
     CdevUpdateFtraceEvent cdev_update = 342;
+    CpuhpExitFtraceEvent cpuhp_exit = 343;
+    CpuhpMultiEnterFtraceEvent cpuhp_multi_enter = 344;
+    CpuhpEnterFtraceEvent cpuhp_enter = 345;
+    CpuhpLatencyFtraceEvent cpuhp_latency = 346;
+    FastrpcDmaStatFtraceEvent fastrpc_dma_stat = 347;
+    DpuTracingMarkWriteFtraceEvent dpu_tracing_mark_write = 348;
+    G2dTracingMarkWriteFtraceEvent g2d_tracing_mark_write = 349;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
index 5e6ea92..1f65456 100644
--- a/protos/perfetto/trace/ftrace/ftrace_stats.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -69,4 +69,15 @@
 
   // Per-CPU stats (one entry for each CPU).
   repeated FtraceCpuStats cpu_stats = 2;
+
+  // When FtraceConfig.symbolize_ksyms = true, this records the number of
+  // symbols parsed from /proc/kallsyms, whether they have been seen in the
+  // trace or not. It can be used to debug kptr_restrict or security-related
+  // errors.
+  // Note: this will be valid only when phase = END_OF_TRACE. The symbolizer is
+  // initialized. When START_OF_TRACE is emitted it is not ready yet.
+  optional uint32 kernel_symbols_parsed = 3;
+
+  // The memory used by the kernel symbolizer (KernelSymbolMap.size_bytes()).
+  optional uint32 kernel_symbols_mem_kb = 4;
 }
diff --git a/protos/perfetto/trace/ftrace/g2d.proto b/protos/perfetto/trace/ftrace/g2d.proto
new file mode 100644
index 0000000..d87c211
--- /dev/null
+++ b/protos/perfetto/trace/ftrace/g2d.proto
@@ -0,0 +1,13 @@
+// Autogenerated by:
+// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
+// Do not edit.
+
+syntax = "proto2";
+package perfetto.protos;
+
+message G2dTracingMarkWriteFtraceEvent {
+  optional int32 pid = 1;
+  optional string name = 4;
+  optional uint32 type = 5;
+  optional int32 value = 6;
+}
diff --git a/protos/perfetto/trace/interned_data/interned_data.proto b/protos/perfetto/trace/interned_data/interned_data.proto
index a330e15..cc9c9e3 100644
--- a/protos/perfetto/trace/interned_data/interned_data.proto
+++ b/protos/perfetto/trace/interned_data/interned_data.proto
@@ -17,6 +17,7 @@
 syntax = "proto2";
 
 import "protos/perfetto/trace/gpu/gpu_render_stage_event.proto";
+import "protos/perfetto/trace/track_event/chrome_histogram_sample.proto";
 import "protos/perfetto/trace/track_event/debug_annotation.proto";
 import "protos/perfetto/trace/track_event/log_message.proto";
 import "protos/perfetto/trace/track_event/track_event.proto";
@@ -52,7 +53,7 @@
 // emitted proactively in advance of referring to them in later packets.
 //
 // Next reserved id: 8 (up to 15).
-// Next id: 25.
+// Next id: 27.
 message InternedData {
   // TODO(eseckler): Replace iid fields inside interned messages with
   // map<iid, message> type fields in InternedData.
@@ -69,6 +70,7 @@
   repeated DebugAnnotationName debug_annotation_names = 3;
   repeated SourceLocation source_locations = 4;
   repeated LogMessageBody log_message_body = 20;
+  repeated HistogramName histogram_names = 25;
 
   // Note: field IDs up to 15 should be used for frequent data only.
 
@@ -99,4 +101,12 @@
 
   // Description of a GPU hardware queue or render stage.
   repeated InternedGpuRenderStageSpecification gpu_specifications = 24;
+
+  // This is set when FtraceConfig.symbolize_ksyms = true.
+  // The id of each symbol the number that will be reported in ftrace events
+  // like sched_block_reason.caller and is obtained from a monotonic counter.
+  // The same symbol can have different indexes in different bundles.
+  // This is is NOT the real address. This is to avoid disclosing KASLR through
+  // traces.
+  repeated InternedString kernel_symbols = 26;
 }
diff --git a/protos/perfetto/trace/memory_graph.proto b/protos/perfetto/trace/memory_graph.proto
new file mode 100644
index 0000000..ec315fb
--- /dev/null
+++ b/protos/perfetto/trace/memory_graph.proto
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Message definitions for app-reported memory breakdowns. At the moment, this
+// is a Chrome-only tracing feature, historically known as 'memory-infra'. See
+// https://chromium.googlesource.com/chromium/src/+/master/docs/memory-infra/ .
+// This is unrelated to the native or java heap profilers (those protos live
+// in //protos/perfetto/trace/profiling/).
+
+message MemoryTrackerSnapshot {
+  // Memory snapshot of a process. The snapshot contains memory data that is
+  // from 2 different sources, namely system stats and instrumentation stats.
+  // The system memory usage stats come from the OS based on standard API
+  // available in the platform to query memory usage. The instrumentation stats
+  // are added by instrumenting specific piece of code which tracks memory
+  // allocations and deallocations made by a small sub-system within the
+  // application.
+  // The system stats of the global memory snapshot are recorded as part of
+  // ProcessStats and SmapsPacket fields in trace packet with the same
+  // timestamp.
+  message ProcessSnapshot {
+    // Process ID of the process
+    optional int32 pid = 1;
+
+    // Memory dumps are represented as a graph of memory nodes which contain
+    // statistics. To avoid double counting the same memory across different
+    // nodes, edges are used to mark nodes that account for the same memory. See
+    // this doc for examples of the usage:
+    // https://docs.google.com/document/d/1WGQRJ1sjJrfVkNcgPVY6frm64UqPc94tsxUOXImZUZI
+
+    // A single node in the memory graph.
+    message MemoryNode {
+      // Unique ID of the node across all processes involved in the global
+      // memory dump. The ID is only unique within this particular global dump
+      // identified by GlobalMemoryDumpPacket.global_dump_id.
+      optional uint64 id = 1;
+
+      // Absolute name is a unique name for the memory node within the process
+      // with ProcessMemoryDump.pid. The name can contain multiple parts
+      // separated by '/', which traces the edges of the node from the root
+      // node.
+      // Eg: "partition_allocator/array_buffers/buffer1" refers to the child
+      // node "buffer1" in a graph structure of:
+      //   root -> partition_allocator -> array_buffers -> buffer1.
+      optional string absolute_name = 2;
+
+      // A weak node means that the instrumentation that added the current node
+      // is unsure about the existence of the actual memory. Unless a "strong"
+      // (non-weak is default) node that has an edge to the current node exists
+      // in the current global dump, the current node will be discarded.
+      optional bool weak = 3;
+
+      // Size of the node in bytes, used to compute the effective size of the
+      // nodes without double counting.
+      optional uint64 size_bytes = 4;
+
+      // Entries in the memory node that contain statistics and additional
+      // debuggable information about the memory. The size of the node is
+      // tracked separately in the |size_bytes| field.
+      message MemoryNodeEntry {
+        optional string name = 1;
+
+        enum Units {
+          UNSPECIFIED = 0;
+          BYTES = 1;
+          COUNT = 2;
+        }
+        optional Units units = 2;
+
+        // Contains either one of uint64 or string value.
+        optional uint64 value_uint64 = 3;
+        optional string value_string = 4;
+      }
+      repeated MemoryNodeEntry entries = 5;
+    }
+    repeated MemoryNode allocator_dumps = 2;
+
+    // A directed edge that connects any 2 nodes in the graph above. These are
+    // in addition to the inherent edges added due to the tree structure of the
+    // node's absolute names.
+    // Node with id |source_id| owns the node with id |target_id|, and has the
+    // effect of attributing the memory usage of target to source. |importance|
+    // is optional and relevant only for the cases of co-ownership, where it
+    // acts as a z-index: the owner with the highest importance will be
+    // attributed target's memory.
+    message MemoryEdge {
+      optional uint64 source_id = 1;
+      optional uint64 target_id = 2;
+      optional uint32 importance = 3;
+      optional bool overridable = 4;
+    }
+    repeated MemoryEdge memory_edges = 3;
+  }
+
+  // Unique ID that represents the global memory dump.
+  optional uint64 global_dump_id = 1;
+
+  enum LevelOfDetail {
+    DETAIL_FULL = 0;
+    DETAIL_LIGHT = 1;
+    DETAIL_BACKGROUND = 2;
+  }
+  optional LevelOfDetail level_of_detail = 2;
+
+  repeated ProcessSnapshot process_memory_dumps = 3;
+}
diff --git a/protos/perfetto/trace/perfetto/tracing_service_event.proto b/protos/perfetto/trace/perfetto/tracing_service_event.proto
index 26453ce..04923dc 100644
--- a/protos/perfetto/trace/perfetto/tracing_service_event.proto
+++ b/protos/perfetto/trace/perfetto/tracing_service_event.proto
@@ -53,5 +53,13 @@
     // after all packets from producers have been included in the central
     // tracing buffer.
     bool tracing_disabled = 5;
+
+    // Emitted if perfetto --save-for-bugreport was invoked while the current
+    // tracing session was running and it had the highest bugreport_score. In
+    // this case the original consumer will see a nearly empty trace, because
+    // the contents are routed onto the bugreport file. This event flags the
+    // situation explicitly. Traces that contain this marker should be discarded
+    // by test infrastructures / pipelines.
+    bool seized_for_bugreport = 6;
   }
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 693fd37..ad28ead 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -323,6 +323,27 @@
   // When enabled, the data source should only fill in fields in the output that
   // are not potentially privacy sensitive.
   optional bool privacy_filtering_enabled = 2;
+
+  // Instead of emitting binary protobuf, convert the trace data to the legacy
+  // JSON format. Note that the trace data will still be returned as a series of
+  // TracePackets, but the embedded data will be JSON instead of serialized
+  // protobuf.
+  optional bool convert_to_legacy_json = 3;
+
+  // Priority of the tracing session client. A higher priority session may
+  // preempt a lower priority one in configurations where concurrent sessions
+  // aren't supported.
+  enum ClientPriority {
+    UNKNOWN = 0;
+    BACKGROUND = 1;
+    USER_INITIATED = 2;
+  }
+  optional ClientPriority client_priority = 4;
+
+  // Applicable only when using legacy JSON format.
+  // If |json_agent_label_filter| is not empty, only data pertaining to
+  // the specified tracing agent label (e.g. "traceEvents") will be returned.
+  optional string json_agent_label_filter = 5;
 }
 
 // End of protos/perfetto/config/chrome/chrome_config.proto
@@ -346,6 +367,19 @@
     optional bool enabled = 1;
   }
   optional CompactSchedConfig compact_sched = 12;
+
+  // Enables symbol name resolution against /proc/kallsyms.
+  // It requires that either traced_probes is running as root or that
+  // kptr_restrict has been manually lowered.
+  // It does not disclose KASLR, symbol addresses are mangled.
+  optional bool symbolize_ksyms = 13;
+
+  // By default the kernel symbolizer is lazily initialized on a deferred task
+  // to reduce ftrace's time-to-start-recording. Unfortunately that makes
+  // ksyms integration tests hard. This flag forces the kernel symbolizer to be
+  // initialized synchronously on the data source start and hence avoiding
+  // timing races in tests.
+  optional bool initialize_ksyms_synchronously_for_testing = 14;
 }
 
 // End of protos/perfetto/config/ftrace/ftrace_config.proto
@@ -412,6 +446,34 @@
 
 // End of protos/perfetto/config/inode_file/inode_file_config.proto
 
+// Begin of protos/perfetto/config/interceptors/console_config.proto
+
+message ConsoleConfig {
+  enum Output {
+    OUTPUT_UNSPECIFIED = 0;
+    OUTPUT_STDOUT = 1;
+    OUTPUT_STDERR = 2;
+  }
+  optional Output output = 1;
+  optional bool enable_colors = 2;
+}
+
+// End of protos/perfetto/config/interceptors/console_config.proto
+
+// Begin of protos/perfetto/config/interceptor_config.proto
+
+// Configuration for trace packet interception. Used for diverting trace data to
+// non-Perfetto sources (e.g., logging to the console, ETW) when using the
+// Perfetto SDK.
+message InterceptorConfig {
+  // Matches the name given to RegisterInterceptor().
+  optional string name = 1;
+
+  optional ConsoleConfig console_config = 100 [lazy = true];
+}
+
+// End of protos/perfetto/config/interceptor_config.proto
+
 // Begin of protos/perfetto/config/power/android_power_config.proto
 
 message AndroidPowerConfig {
@@ -493,7 +555,7 @@
 // Begin of protos/perfetto/config/profiling/heapprofd_config.proto
 
 // Configuration for go/heapprofd.
-// Next id: 21
+// Next id: 24
 message HeapprofdConfig {
   message ContinuousDumpConfig {
     // ms to wait before first dump.
@@ -502,12 +564,23 @@
     optional uint32 dump_interval_ms = 6;
   }
 
+  // Sampling rate for all heaps not specified via heap_sampling_intervals.
+  //
+  // These are:
+  // * All heaps if heap_sampling_intervals is empty.
+  // * Those profiled due to all_heaps and not named in heaps if
+  //   heap_sampling_intervals is not empty.
+  // * The implicit libc.malloc heap if heaps is empty.
+  //
   // Set to 1 for perfect accuracy.
   // Otherwise, sample every sample_interval_bytes on average.
   //
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#sampling-interval
   // for more details.
+  //
+  // BUGS
+  // Before Android 12, setting this to 0 would crash the target process.
   optional uint64 sampling_interval_bytes = 1;
 
   // E.g. surfaceflinger, com.android.phone
@@ -522,10 +595,29 @@
   // For watermark based triggering or local debugging.
   repeated uint64 pid = 4;
 
-  // Which heaps to sample, e.g. "malloc". If left empty, only samples
+  // Which heaps to sample, e.g. "libc.malloc". If left empty, only samples
   // "malloc".
+  //
+  // Introduced in Android 12.
   repeated string heaps = 20;
 
+  optional bool stream_allocations = 23;
+
+  // If given, needs to be the same length as heaps and gives the sampling
+  // interval for the respective entry in heaps.
+  //
+  // Otherwise, sampling_interval_bytes is used.
+  //
+  // It is recommended to set sampling_interval_bytes to a reasonable default
+  // value when using this, as a value of 0 for sampling_interval_bytes will
+  // crash the target process before Android 12.
+  //
+  // Introduced in Android 12.
+  repeated uint64 heap_sampling_intervals = 22;
+
+  // Sample all heaps registered by target process. Introduced in Android 12.
+  optional bool all_heaps = 21;
+
   // Profile all processes eligible for profiling on the system.
   // See
   // https://perfetto.dev/docs/data-sources/native-heap-profiler#heapprofd-targets
@@ -592,17 +684,8 @@
   // Introduced in Android 11.
   optional bool no_running = 11;
 
-  // Gather information on how many bytes of allocations are on non-referenced
-  // pages. The way to use this generally is:
-  // 1. Start profile of app.
-  // 2. Start app.
-  // 3. Trigger a dump by sending SIGUSR1 to heapprofd.
-  // 4. Do operations.
-  // 5. End profile.
-  //
-  // You can then find the allocations that were not used for the operations you
-  // did in step 4.
-  optional bool idle_allocations = 12;
+  // deprecated idle_allocations.
+  reserved 12;
 
   // Cause heapprofd to emit a single dump at the end, showing the memory usage
   // at the point in time when the sampled heap usage of the process was at its
@@ -665,6 +748,10 @@
   // * start with /data/app
   // * contain "extracted in memory from Y", where Y matches any of the above
   optional bool dump_smaps = 5;
+
+  // Exclude objects of the following types from the profile. This can be
+  // useful if lots of uninteresting objects, e.g. "sun.misc.Cleaner".
+  repeated string ignored_types = 6;
 }
 
 // End of protos/perfetto/config/profiling/java_hprof_config.proto
@@ -675,6 +762,8 @@
 //
 // At the time of writing, the config options are restricted to the periodic
 // system-wide stack sampling use-case (|all_cpus| must be true).
+//
+// Next id: 14
 message PerfEventConfig {
   // If true, sample events on all CPUs.
   optional bool all_cpus = 1;
@@ -693,27 +782,52 @@
   // If unset, an implementation-defined default is used.
   optional uint32 ring_buffer_pages = 3;
 
-  // Process ID (TGID) whitelist. If this list is not empty, only matching
-  // samples will be retained. If multiple whitelists and blacklists are
+  // If true, callstacks will include the kernel-space frames. Such frames can
+  // be identified by a magical "kernel" string as their mapping name.
+  // Requires traced_perf to be running as root, or kptr_restrict to have been
+  // manually unrestricted.
+  // This does *not* disclose KASLR, as only the function names are emitted.
+  optional bool kernel_frames = 12;
+
+  //
+  // Target process selection:
+  //
+
+  // Process ID (TGID) allowlist. If this list is not empty, only matching
+  // samples will be retained. If multiple allow/deny-lists are
   // specified by the config, then all of them are evaluated for each sampled
   // process.
   repeated int32 target_pid = 4;
 
-  // Command line whitelist, matched against the
+  // Command line allowlist, matched against the
   // /proc/<pid>/cmdline (not the comm string), with both sides being
   // "normalized". Normalization is as follows: (1) trim everything beyond the
   // first null or "@" byte; (2) if the string contains forward slashes, trim
   // everything up to and including the last one.
   repeated string target_cmdline = 5;
 
-  // PID blacklist.
+  // List of excluded pids.
   repeated int32 exclude_pid = 6;
 
-  // Command line blacklist. Normalized in the same way as |target_cmdline|.
+  // List of excluded cmdlines. Normalized in the same way as |target_cmdline|.
   repeated string exclude_cmdline = 7;
 
-  ////////////////////
+  // Number of additional command lines to sample. Only those which are neither
+  // explicitly included nor excluded will be considered. Processes are accepted
+  // on a first come, first served basis.
+  optional uint32 additional_cmdline_count = 11;
+
+  //
+  // Daemon's resource usage limits:
+  //
+
+  // Stop the data source if traced_perf's combined {RssAnon + Swap} memory
+  // footprint exceeds this value.
+  optional uint32 max_daemon_memory_kb = 13;
+
+  //
   // Uncommon options:
+  //
 
   // Timeout for the remote /proc/<pid>/{maps,mem} file descriptors for a
   // sampled process. This is primarily for Android, where this lookup is
@@ -875,6 +989,39 @@
   VMSTAT_NR_ZSPAGES = 93;
   VMSTAT_NR_ION_HEAP = 94;
   VMSTAT_NR_GPU_HEAP = 95;
+  VMSTAT_ALLOCSTALL_DMA = 96;
+  VMSTAT_ALLOCSTALL_MOVABLE = 97;
+  VMSTAT_ALLOCSTALL_NORMAL = 98;
+  VMSTAT_COMPACT_DAEMON_FREE_SCANNED = 99;
+  VMSTAT_COMPACT_DAEMON_MIGRATE_SCANNED = 100;
+  VMSTAT_NR_FASTRPC = 101;
+  VMSTAT_NR_INDIRECTLY_RECLAIMABLE = 102;
+  VMSTAT_NR_ION_HEAP_POOL = 103;
+  VMSTAT_NR_KERNEL_MISC_RECLAIMABLE = 104;
+  VMSTAT_NR_SHADOW_CALL_STACK_BYTES = 105;
+  VMSTAT_NR_SHMEM_HUGEPAGES = 106;
+  VMSTAT_NR_SHMEM_PMDMAPPED = 107;
+  VMSTAT_NR_UNRECLAIMABLE_PAGES = 108;
+  VMSTAT_NR_ZONE_ACTIVE_ANON = 109;
+  VMSTAT_NR_ZONE_ACTIVE_FILE = 110;
+  VMSTAT_NR_ZONE_INACTIVE_ANON = 111;
+  VMSTAT_NR_ZONE_INACTIVE_FILE = 112;
+  VMSTAT_NR_ZONE_UNEVICTABLE = 113;
+  VMSTAT_NR_ZONE_WRITE_PENDING = 114;
+  VMSTAT_OOM_KILL = 115;
+  VMSTAT_PGLAZYFREE = 116;
+  VMSTAT_PGLAZYFREED = 117;
+  VMSTAT_PGREFILL = 118;
+  VMSTAT_PGSCAN_DIRECT = 119;
+  VMSTAT_PGSCAN_KSWAPD = 120;
+  VMSTAT_PGSKIP_DMA = 121;
+  VMSTAT_PGSKIP_MOVABLE = 122;
+  VMSTAT_PGSKIP_NORMAL = 123;
+  VMSTAT_PGSTEAL_DIRECT = 124;
+  VMSTAT_PGSTEAL_KSWAPD = 125;
+  VMSTAT_SWAP_RA = 126;
+  VMSTAT_SWAP_RA_HIT = 127;
+  VMSTAT_WORKINGSET_RESTORE = 128;
 }
 // End of protos/perfetto/common/sys_stats_counters.proto
 
@@ -1024,6 +1171,7 @@
 // Begin of protos/perfetto/config/data_source_config.proto
 
 // The configuration that is passed to each data source when starting tracing.
+// Next id: 116
 message DataSourceConfig {
   // Data source unique name, e.g., "linux.ftrace". This must match
   // the name passed by the data source when it registers (see
@@ -1056,6 +1204,7 @@
   // one of which produces metadata for the other one, belong to the same trace
   // session and hence should be linked together.
   // This field was introduced in Aug 2018 after Android P.
+  // DO NOT SET in consumer as this will be overridden by the service.
   optional uint64 tracing_session_id = 4;
 
   // Keeep the lower IDs (up to 99) for fields that are *not* specific to
@@ -1104,6 +1253,16 @@
   // C++ class for it so it can pass around plain C++ objets.
   optional ChromeConfig chrome_config = 101;
 
+  // If an interceptor is specified here, packets for this data source will be
+  // rerouted to the interceptor instead of the main trace buffer. This can be
+  // used, for example, to write trace data into ETW or for logging trace points
+  // to the console.
+  //
+  // Note that interceptors are only supported by data sources registered
+  // through the Perfetto SDK API. Data sources that don't use that API (e.g.,
+  // traced_probes) may not support interception.
+  optional InterceptorConfig interceptor_config = 115;
+
   // This is a fallback mechanism to send a free-form text config to the
   // producer. In theory this should never be needed. All the code that
   // is part of the platform (i.e. traced service) is supposed to *not* truncate
@@ -1129,7 +1288,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 30.
+// Next id: 31.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -1327,6 +1486,15 @@
   // trace ends to notify it about the trace readiness.
   optional bool notify_traceur = 16;
 
+  // Android-only. If set to a value > 0, marks the trace session as a candidate
+  // for being attached to a bugreport. This field effectively acts as a z-index
+  // for bugreports. When Android's dumpstate runs perfetto
+  // --save-for-bugreport, traced will pick the tracing session with the highest
+  // score (score <= 0 is ignored), will steal its contents, save the trace into
+  // a known path and stop prematurely.
+  // This field was introduced in Android S.
+  optional int32 bugreport_score = 30;
+
   // Triggers allow producers to start or stop the tracing session when an event
   // occurs.
   //
@@ -1660,6 +1828,111 @@
 
 // End of protos/perfetto/trace/android/android_log.proto
 
+// Begin of protos/perfetto/trace/android/frame_timeline_event.proto
+
+// Generated by SurfaceFlinger's FrameTimeline (go/adaptive-scheduling-fr).
+// Used in comparing the expected timeline of a frame to the actual timeline.
+// Key terms:
+//    1) DisplayFrame - represents SurfaceFlinger's work on a frame(composited)
+//    2) SurfaceFrame - represents App's work on its frame
+//    3) Timeline = start to end of a component's(app/SF) work on a frame.
+// SurfaceFlinger composites frames from many apps together, so
+//    One DisplayFrame can map to N SurfaceFrame(s)
+// This relationship can be reconstructed by using
+//    DisplayFrame.token = SurfaceFrame.display_frame_token
+message FrameTimelineEvent {
+  // Specifies which component was the main source for the jank.
+  enum JankType {
+    JANK_UNSPECIFIED = 0;
+    JANK_NONE = 1;
+    JANK_SF_SCHEDULING = 2;
+    JANK_PREDICTION_ERROR = 3;
+    JANK_DISPLAY_HAL = 4;
+    JANK_SF_DEADLINE_MISSED = 5;
+    JANK_APP_DEADLINE_MISSED = 6;
+    JANK_BUFFER_STUFFING = 7;
+    JANK_UNKNOWN = 8;
+  };
+
+  // Specifies how a frame was presented on screen w.r.t. timing.
+  // Can be different for SurfaceFrame and DisplayFrame.
+  enum PresentType {
+    PRESENT_UNSPECIFIED = 0;
+    PRESENT_ON_TIME = 1;
+    PRESENT_LATE = 2;
+    PRESENT_EARLY = 3;
+    PRESENT_DROPPED = 4;
+  };
+
+  // Represents the app's work on a frame.
+  // Next id: 13
+  message SurfaceFrame {
+    // Token received by the app for its work. Can be shared between multiple
+    // layers of the same app (example: pip mode).
+    optional int64 token = 1;
+
+    // The corresponding DisplayFrame token is required to link the App's work
+    // with SurfaceFlinger's work. Many SurfaceFrames can be mapped to a single
+    // DisplayFrame.
+    // this.display_frame_token = DisplayFrame.token
+    optional int64 display_frame_token = 12;
+
+    optional PresentType present_type = 2;
+    optional bool on_time_finish = 3;
+    optional bool gpu_composition = 4;
+    optional JankType jank_type = 5;
+
+    // Timestamps in nanoseconds using CLOCK_MONOTONIC.
+    // Expected timeline = expected_start to expected_end.
+    optional int64 expected_start_ns = 6;
+    optional int64 expected_end_ns = 7;
+
+    // (b/172587309) Apps currently do not provide actual start time.
+    // Actual timeline = expected_start to actual_end.
+    optional int64 actual_start_ns = 8;
+    // If two SufaceFrames have the same token and same pid, then
+    //  actual timeline =
+    //    expected_start to max(frame1.actual_end, frame2.actual_end)
+    optional int64 actual_end_ns = 9;
+
+    // Pid of the app. Used in creating the timeline tracks (and slices) inside
+    // the respective process track group.
+    optional int32 pid = 10;
+    optional string layer_name = 11;
+  };
+
+  // Represents the SurfaceFlinger's work on a frame.
+  message DisplayFrame {
+    // Token received by SurfaceFlinger for its work
+    // this.token = SurfaceFrame.display_frame_token
+    optional int64 token = 1;
+    optional PresentType present_type = 2;
+    optional bool on_time_finish = 3;
+    optional bool gpu_composition = 4;
+    optional JankType jank_type = 5;
+
+    // Timestamps in nanoseconds using CLOCK_MONOTONIC.
+    // Expected timeline = expected_start to expected_end.
+    optional int64 expected_start_ns = 6;
+    optional int64 expected_end_ns = 7;
+
+    // Actual timeline = actual_start to actual_end.
+    optional int64 actual_start_ns = 8;
+    optional int64 actual_end_ns = 9;
+
+    // Pid of SurfaceFlinger. Used in creating the timeline tracks (and slices)
+    // inside the SurfaceFlinger process group.
+    optional int32 pid = 10;
+  };
+
+  oneof event {
+    DisplayFrame display_frame = 1;
+    SurfaceFrame surface_frame = 2;
+  }
+}
+
+// End of protos/perfetto/trace/android/frame_timeline_event.proto
+
 // Begin of protos/perfetto/trace/android/gpu_mem_event.proto
 
 // Generated by Android's GpuService.
@@ -2570,6 +2843,48 @@
 
 // End of protos/perfetto/trace/ftrace/compaction.proto
 
+// Begin of protos/perfetto/trace/ftrace/cpuhp.proto
+
+message CpuhpExitFtraceEvent {
+  optional uint32 cpu = 1;
+  optional int32 idx = 2;
+  optional int32 ret = 3;
+  optional int32 state = 4;
+}
+message CpuhpMultiEnterFtraceEvent {
+  optional uint32 cpu = 1;
+  optional uint64 fun = 2;
+  optional int32 idx = 3;
+  optional int32 target = 4;
+}
+message CpuhpEnterFtraceEvent {
+  optional uint32 cpu = 1;
+  optional uint64 fun = 2;
+  optional int32 idx = 3;
+  optional int32 target = 4;
+}
+message CpuhpLatencyFtraceEvent {
+  optional uint32 cpu = 1;
+  optional int32 ret = 2;
+  optional uint32 state = 3;
+  optional uint64 time = 4;
+}
+
+// End of protos/perfetto/trace/ftrace/cpuhp.proto
+
+// Begin of protos/perfetto/trace/ftrace/dpu.proto
+
+message DpuTracingMarkWriteFtraceEvent {
+  optional int32 pid = 1;
+  optional string trace_name = 2;
+  optional uint32 trace_begin = 3;
+  optional string name = 4;
+  optional uint32 type = 5;
+  optional int32 value = 6;
+}
+
+// End of protos/perfetto/trace/ftrace/dpu.proto
+
 // Begin of protos/perfetto/trace/ftrace/ext4.proto
 
 message Ext4DaWriteBeginFtraceEvent {
@@ -3476,6 +3791,16 @@
 
 // End of protos/perfetto/trace/ftrace/f2fs.proto
 
+// Begin of protos/perfetto/trace/ftrace/fastrpc.proto
+
+message FastrpcDmaStatFtraceEvent {
+  optional int32 cid = 1;
+  optional int64 len = 2;
+  optional uint64 total_allocated = 3;
+}
+
+// End of protos/perfetto/trace/ftrace/fastrpc.proto
+
 // Begin of protos/perfetto/trace/ftrace/fence.proto
 
 message FenceInitFtraceEvent {
@@ -3533,6 +3858,17 @@
 
 // End of protos/perfetto/trace/ftrace/ftrace.proto
 
+// Begin of protos/perfetto/trace/ftrace/g2d.proto
+
+message G2dTracingMarkWriteFtraceEvent {
+  optional int32 pid = 1;
+  optional string name = 4;
+  optional uint32 type = 5;
+  optional int32 value = 6;
+}
+
+// End of protos/perfetto/trace/ftrace/g2d.proto
+
 // Begin of protos/perfetto/trace/ftrace/generic.proto
 
 // This generic proto is used to output events in the trace
@@ -4776,6 +5112,13 @@
     GpuMemTotalFtraceEvent gpu_mem_total = 340;
     ThermalTemperatureFtraceEvent thermal_temperature = 341;
     CdevUpdateFtraceEvent cdev_update = 342;
+    CpuhpExitFtraceEvent cpuhp_exit = 343;
+    CpuhpMultiEnterFtraceEvent cpuhp_multi_enter = 344;
+    CpuhpEnterFtraceEvent cpuhp_enter = 345;
+    CpuhpLatencyFtraceEvent cpuhp_latency = 346;
+    FastrpcDmaStatFtraceEvent fastrpc_dma_stat = 347;
+    DpuTracingMarkWriteFtraceEvent dpu_tracing_mark_write = 348;
+    G2dTracingMarkWriteFtraceEvent g2d_tracing_mark_write = 349;
   }
 }
 
@@ -4882,6 +5225,17 @@
 
   // Per-CPU stats (one entry for each CPU).
   repeated FtraceCpuStats cpu_stats = 2;
+
+  // When FtraceConfig.symbolize_ksyms = true, this records the number of
+  // symbols parsed from /proc/kallsyms, whether they have been seen in the
+  // trace or not. It can be used to debug kptr_restrict or security-related
+  // errors.
+  // Note: this will be valid only when phase = END_OF_TRACE. The symbolizer is
+  // initialized. When START_OF_TRACE is emitted it is not ready yet.
+  optional uint32 kernel_symbols_parsed = 3;
+
+  // The memory used by the kernel symbolizer (KernelSymbolMap.size_bytes()).
+  optional uint32 kernel_symbols_mem_kb = 4;
 }
 
 // End of protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -5323,6 +5677,26 @@
 
 // End of protos/perfetto/trace/profiling/profile_common.proto
 
+// Begin of protos/perfetto/trace/track_event/chrome_histogram_sample.proto
+
+message HistogramName {
+  optional uint64 iid = 1;
+  optional string name = 2;
+}
+
+// An individual histogram sample logged via Chrome's UMA metrics system.
+message ChromeHistogramSample {
+  // MD5 hash of the metric name. Either |name_hash| or |name|/|name_iid| or
+  // both must be present.
+  optional uint64 name_hash = 1;
+  optional string name = 2;
+  optional int64 sample = 3;
+  // Interned HistogramName. Only one of |name|, |name_iid| can be set.
+  optional uint64 name_iid = 4;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_histogram_sample.proto
+
 // Begin of protos/perfetto/trace/track_event/debug_annotation.proto
 
 // Key/value annotations provided in untyped TRACE_EVENT macros. These
@@ -5420,6 +5794,25 @@
 
 // End of protos/perfetto/trace/track_event/source_location.proto
 
+// Begin of protos/perfetto/trace/track_event/chrome_application_state_info.proto
+
+
+// Trace event arguments for application state changes.
+message ChromeApplicationStateInfo {
+  // Enum definition taken from:
+  // https://source.chromium.org/chromium/chromium/src/+/master:base/android/application_status_listener.h
+  enum ChromeApplicationState {
+    APPLICATION_STATE_UNKNOWN = 0;
+    APPLICATION_STATE_HAS_RUNNING_ACTIVITIES = 1;
+    APPLICATION_STATE_HAS_PAUSED_ACTIVITIES = 2;
+    APPLICATION_STATE_HAS_STOPPED_ACTIVITIES = 3;
+    APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES = 4;
+  };
+  optional ChromeApplicationState application_state = 1;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_application_state_info.proto
+
 // Begin of protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto
 
 // Describes Chrome's Compositor scheduler's current state and associated
@@ -5716,23 +6109,15 @@
 
   optional uint64 frame_source = 3;
   optional uint64 frame_sequence = 4;
+
+  // If this is a droped frame (i.e. if |state| is set to |STATE_DROPPED| or
+  // |STATE_PRESENTED_PARTIAL|), then indicates whether this frame impacts
+  // smoothness.
+  optional bool affects_smoothness = 5;
 }
 
 // End of protos/perfetto/trace/track_event/chrome_frame_reporter.proto
 
-// Begin of protos/perfetto/trace/track_event/chrome_histogram_sample.proto
-
-// An individual histogram sample logged via Chrome's UMA metrics system.
-message ChromeHistogramSample {
-  // MD5 hash of the metric name. Either |name_hash| or |name| or both
-  // must be present.
-  optional uint64 name_hash = 1;
-  optional string name = 2;
-  optional int64 sample = 3;
-}
-
-// End of protos/perfetto/trace/track_event/chrome_histogram_sample.proto
-
 // Begin of protos/perfetto/trace/track_event/chrome_keyed_service.proto
 
 // Details about one of Chrome's keyed services associated with the event.
@@ -5870,6 +6255,52 @@
 
 // End of protos/perfetto/trace/track_event/chrome_legacy_ipc.proto
 
+// Begin of protos/perfetto/trace/track_event/chrome_message_pump.proto
+
+// Details about Chrome message pump events
+message ChromeMessagePump {
+  // True if there are sent messages in the queue.
+  optional bool sent_messages_in_queue = 1;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_message_pump.proto
+
+// Begin of protos/perfetto/trace/track_event/chrome_mojo_event_info.proto
+
+// Contains information to identify mojo handling events. The trace events in
+// mojo are common for all mojo interfaces and this information is used to
+// identify who is the caller or callee.
+message ChromeMojoEventInfo {
+  // Contains the interface name or the file name of the creator of a mojo
+  // handle watcher, recorded when an event if notified to the watcher. The code
+  // that runs within the track event belongs to the interface.
+  optional string watcher_notify_interface_tag = 1;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_mojo_event_info.proto
+
+// Begin of protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto
+
+// Describes the state of the RendererScheduler for a given Renderer Process.
+
+// RAIL Mode is an indication of the kind of work that a Renderer is currently
+// performing which is in turn used to prioritise work accordingly.
+// A fuller description of these modes can be found https://web.dev/rail/
+enum ChromeRAILMode {
+  RAIL_MODE_NONE = 0;
+  RAIL_MODE_RESPONSE = 1;
+  RAIL_MODE_ANIMATION = 2;
+  RAIL_MODE_IDLE = 3;
+  RAIL_MODE_LOAD = 4;
+}
+
+// Next id: 2
+message ChromeRendererSchedulerState {
+  optional ChromeRAILMode rail_mode = 1;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto
+
 // Begin of protos/perfetto/trace/track_event/chrome_user_event.proto
 
 // Details about a UI interaction initiated by the user, such as opening or
@@ -5886,6 +6317,16 @@
 
 // End of protos/perfetto/trace/track_event/chrome_user_event.proto
 
+// Begin of protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
+
+// Details about HWNDMessageHandler trace events.
+message ChromeWindowHandleEventInfo {
+  optional uint32 dpi = 1;
+  optional uint32 message_id = 2;
+}
+
+// End of protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
+
 // Begin of protos/perfetto/trace/track_event/task_execution.proto
 
 // TrackEvent arguments describing the execution of a task.
@@ -5907,6 +6348,7 @@
 // referred to via the track's UUID.
 //
 // A simple TrackEvent packet specifies a timestamp, category, name and type:
+// ```protobuf
 //   trace_packet {
 //     timestamp: 1000
 //     track_event {
@@ -5915,9 +6357,11 @@
 //       type: TYPE_INSTANT
 //      }
 //    }
+// ```
 //
 // To associate an event with a custom track (e.g. a thread), the track is
 // defined in a separate packet and referred to from the TrackEvent by its UUID:
+// ```protobuf
 //   trace_packet {
 //     track_descriptor {
 //       track_uuid: 1234
@@ -5931,8 +6375,11 @@
 //       }
 //     }
 //   }
+// ```
 //
 // A pair of TYPE_SLICE_BEGIN and _END events form a slice on the track:
+//
+// ```protobuf
 //   trace_packet {
 //     timestamp: 1200
 //     track_event {
@@ -5949,7 +6396,7 @@
 //       type: TYPE_SLICE_END
 //     }
 //   }
-//
+// ```
 // TrackEvents also support optimizations to reduce data repetition and encoded
 // data size, e.g. through data interning (names, categories, ...) and delta
 // encoding of timestamps/counters. For details, see the InternedData message.
@@ -5957,7 +6404,7 @@
 // their default track association) can be emitted as part of a
 // TrackEventDefaults message.
 //
-// Next reserved id: 13 (up to 15). Next id: 33.
+// Next reserved id: 13 (up to 15). Next id: 43.
 message TrackEvent {
   // Names of categories of the event. In the client library, categories are a
   // way to turn groups of individual events on or off.
@@ -6048,7 +6495,27 @@
   repeated uint64 extra_counter_track_uuids = 31;
   repeated int64 extra_counter_values = 12;
 
-  // TODO(eseckler): Add flow event support.
+  // IDs of flows originating, passing through, or ending at this event.
+  // Flow IDs are global within a trace.
+  //
+  // A flow connects a sequence of TrackEvents within or across tracks, e.g.
+  // an input event may be handled on one thread but cause another event on
+  // a different thread - a flow between the two events can associate them.
+  //
+  // The direction of the flows between events is inferred from the events'
+  // timestamps. The earliest event with the same flow ID becomes the source
+  // of the flow. Any events thereafter are intermediate steps of the flow,
+  // until the flow terminates at the last event with the flow ID.
+  //
+  // Flows can also be explicitly terminated (see |terminating_flow_ids|), so
+  // that the same ID can later be reused for another flow.
+  repeated uint64 flow_ids = 36;
+
+  // List of flow ids which should terminate on this event, otherwise same as
+  // |flow_ids|.
+  // Any one flow ID should be either listed as part of |flow_ids| OR
+  // |terminating_flow_ids|, not both.
+  repeated uint64 terminating_flow_ids = 42;
 
   // ---------------------------------------------------------------------------
   // TrackEvent arguments:
@@ -6068,6 +6535,24 @@
   optional ChromeHistogramSample chrome_histogram_sample = 28;
   optional ChromeLatencyInfo chrome_latency_info = 29;
   optional ChromeFrameReporter chrome_frame_reporter = 32;
+  optional ChromeApplicationStateInfo chrome_application_state_info = 39;
+  optional ChromeRendererSchedulerState chrome_renderer_scheduler_state = 40;
+  optional ChromeWindowHandleEventInfo chrome_window_handle_event_info = 41;
+
+  // This field is used only if the source location represents the function that
+  // executes during this event.
+  oneof source_location_field {
+    // Non-interned field.
+    SourceLocation source_location = 33;
+    // TODO(ssid): The interned source locations are not parsed by trace
+    // processor.
+    // Interned field.
+    uint64 source_location_iid = 34;
+  }
+
+  optional ChromeMessagePump chrome_message_pump = 35;
+  optional ChromeMojoEventInfo chrome_mojo_event_info = 38;
+
   // New argument types go here :)
 
   // Extension range for typed events defined externally.
@@ -6255,7 +6740,7 @@
 // emitted proactively in advance of referring to them in later packets.
 //
 // Next reserved id: 8 (up to 15).
-// Next id: 25.
+// Next id: 27.
 message InternedData {
   // TODO(eseckler): Replace iid fields inside interned messages with
   // map<iid, message> type fields in InternedData.
@@ -6272,6 +6757,7 @@
   repeated DebugAnnotationName debug_annotation_names = 3;
   repeated SourceLocation source_locations = 4;
   repeated LogMessageBody log_message_body = 20;
+  repeated HistogramName histogram_names = 25;
 
   // Note: field IDs up to 15 should be used for frequent data only.
 
@@ -6302,10 +6788,126 @@
 
   // Description of a GPU hardware queue or render stage.
   repeated InternedGpuRenderStageSpecification gpu_specifications = 24;
+
+  // This is set when FtraceConfig.symbolize_ksyms = true.
+  // The id of each symbol the number that will be reported in ftrace events
+  // like sched_block_reason.caller and is obtained from a monotonic counter.
+  // The same symbol can have different indexes in different bundles.
+  // This is is NOT the real address. This is to avoid disclosing KASLR through
+  // traces.
+  repeated InternedString kernel_symbols = 26;
 }
 
 // End of protos/perfetto/trace/interned_data/interned_data.proto
 
+// Begin of protos/perfetto/trace/memory_graph.proto
+
+// Message definitions for app-reported memory breakdowns. At the moment, this
+// is a Chrome-only tracing feature, historically known as 'memory-infra'. See
+// https://chromium.googlesource.com/chromium/src/+/master/docs/memory-infra/ .
+// This is unrelated to the native or java heap profilers (those protos live
+// in //protos/perfetto/trace/profiling/).
+
+message MemoryTrackerSnapshot {
+  // Memory snapshot of a process. The snapshot contains memory data that is
+  // from 2 different sources, namely system stats and instrumentation stats.
+  // The system memory usage stats come from the OS based on standard API
+  // available in the platform to query memory usage. The instrumentation stats
+  // are added by instrumenting specific piece of code which tracks memory
+  // allocations and deallocations made by a small sub-system within the
+  // application.
+  // The system stats of the global memory snapshot are recorded as part of
+  // ProcessStats and SmapsPacket fields in trace packet with the same
+  // timestamp.
+  message ProcessSnapshot {
+    // Process ID of the process
+    optional int32 pid = 1;
+
+    // Memory dumps are represented as a graph of memory nodes which contain
+    // statistics. To avoid double counting the same memory across different
+    // nodes, edges are used to mark nodes that account for the same memory. See
+    // this doc for examples of the usage:
+    // https://docs.google.com/document/d/1WGQRJ1sjJrfVkNcgPVY6frm64UqPc94tsxUOXImZUZI
+
+    // A single node in the memory graph.
+    message MemoryNode {
+      // Unique ID of the node across all processes involved in the global
+      // memory dump. The ID is only unique within this particular global dump
+      // identified by GlobalMemoryDumpPacket.global_dump_id.
+      optional uint64 id = 1;
+
+      // Absolute name is a unique name for the memory node within the process
+      // with ProcessMemoryDump.pid. The name can contain multiple parts
+      // separated by '/', which traces the edges of the node from the root
+      // node.
+      // Eg: "partition_allocator/array_buffers/buffer1" refers to the child
+      // node "buffer1" in a graph structure of:
+      //   root -> partition_allocator -> array_buffers -> buffer1.
+      optional string absolute_name = 2;
+
+      // A weak node means that the instrumentation that added the current node
+      // is unsure about the existence of the actual memory. Unless a "strong"
+      // (non-weak is default) node that has an edge to the current node exists
+      // in the current global dump, the current node will be discarded.
+      optional bool weak = 3;
+
+      // Size of the node in bytes, used to compute the effective size of the
+      // nodes without double counting.
+      optional uint64 size_bytes = 4;
+
+      // Entries in the memory node that contain statistics and additional
+      // debuggable information about the memory. The size of the node is
+      // tracked separately in the |size_bytes| field.
+      message MemoryNodeEntry {
+        optional string name = 1;
+
+        enum Units {
+          UNSPECIFIED = 0;
+          BYTES = 1;
+          COUNT = 2;
+        }
+        optional Units units = 2;
+
+        // Contains either one of uint64 or string value.
+        optional uint64 value_uint64 = 3;
+        optional string value_string = 4;
+      }
+      repeated MemoryNodeEntry entries = 5;
+    }
+    repeated MemoryNode allocator_dumps = 2;
+
+    // A directed edge that connects any 2 nodes in the graph above. These are
+    // in addition to the inherent edges added due to the tree structure of the
+    // node's absolute names.
+    // Node with id |source_id| owns the node with id |target_id|, and has the
+    // effect of attributing the memory usage of target to source. |importance|
+    // is optional and relevant only for the cases of co-ownership, where it
+    // acts as a z-index: the owner with the highest importance will be
+    // attributed target's memory.
+    message MemoryEdge {
+      optional uint64 source_id = 1;
+      optional uint64 target_id = 2;
+      optional uint32 importance = 3;
+      optional bool overridable = 4;
+    }
+    repeated MemoryEdge memory_edges = 3;
+  }
+
+  // Unique ID that represents the global memory dump.
+  optional uint64 global_dump_id = 1;
+
+  enum LevelOfDetail {
+    DETAIL_FULL = 0;
+    DETAIL_LIGHT = 1;
+    DETAIL_BACKGROUND = 2;
+  }
+  optional LevelOfDetail level_of_detail = 2;
+
+  repeated ProcessSnapshot process_memory_dumps = 3;
+}
+
+// End of protos/perfetto/trace/memory_graph.proto
+
 // Begin of protos/perfetto/trace/perfetto/perfetto_metatrace.proto
 
 // Used to trace the execution of perfetto itself.
@@ -6380,6 +6982,14 @@
     // after all packets from producers have been included in the central
     // tracing buffer.
     bool tracing_disabled = 5;
+
+    // Emitted if perfetto --save-for-bugreport was invoked while the current
+    // tracing session was running and it had the highest bugreport_score. In
+    // this case the original consumer will see a nearly empty trace, because
+    // the contents are routed onto the bugreport file. This event flags the
+    // situation explicitly. Traces that contain this marker should be discarded
+    // by test infrastructures / pipelines.
+    bool seized_for_bugreport = 6;
   }
 }
 
@@ -6442,7 +7052,7 @@
 
 // End of protos/perfetto/trace/power/power_rails.proto
 
-// Begin of protos/perfetto/trace/profiling/heap_graph.proto
+// Begin of protos/perfetto/trace/profiling/deobfuscation.proto
 
 message ObfuscatedMember {
   // This is the obfuscated field name relative to the class containing the
@@ -6458,7 +7068,9 @@
 message ObfuscatedClass {
   optional string obfuscated_name = 1;
   optional string deobfuscated_name = 2;
+  // fields.
   repeated ObfuscatedMember obfuscated_members = 3;
+  repeated ObfuscatedMember obfuscated_methods = 4;
 }
 
 message DeobfuscationMapping {
@@ -6466,6 +7078,9 @@
   optional int64 version_code = 2;
   repeated ObfuscatedClass obfuscated_classes = 3;
 }
+// End of protos/perfetto/trace/profiling/deobfuscation.proto
+
+// Begin of protos/perfetto/trace/profiling/heap_graph.proto
 
 message HeapGraphRoot {
   enum Type {
@@ -6492,24 +7107,55 @@
 }
 
 message HeapGraphType {
+  enum Kind {
+    KIND_UNKNOWN = 0;
+    KIND_NORMAL = 1;
+    KIND_NOREFERENCES = 2;
+    KIND_STRING = 3;
+    KIND_ARRAY = 4;
+    KIND_CLASS = 5;
+    KIND_CLASSLOADER = 6;
+    KIND_DEXCACHE = 7;
+    KIND_SOFT_REFERENCE = 8;
+    KIND_WEAK_REFERENCE = 9;
+    KIND_FINALIZER_REFERENCE = 10;
+    KIND_PHANTOM_REFERENCE = 11;
+  };
   // TODO(fmayer): Consider removing this and using the index in the repeaed
   // field to save space.
   optional uint64 id = 1;
   optional uint64 location_id = 2;
   optional string class_name = 3;
+  // Size of objects of this type.
+  optional uint64 object_size = 4;
+  optional uint64 superclass_id = 5;
+  // Indices for InternedData.field_names for the names of the fields of
+  // instances of this class. This does NOT include the fields from
+  // superclasses. The consumer of this data needs to walk all super
+  // classes to get a full lists of fields. Objects always write the
+  // fields in order of most specific class to the furthest up superclass.
+  repeated uint64 reference_field_id = 6 [packed = true];
+  optional Kind kind = 7;
+  optional uint64 classloader_id = 8;
 }
 
 message HeapGraphObject {
   optional uint64 id = 1;
 
-  // Index for InternedData.type_names for the name of the type of this object.
+  // Index for InternedData.types for the name of the type of this object.
   optional uint64 type_id = 2;
 
   // Bytes occupied by this objects.
   optional uint64 self_size = 3;
 
+  // Add this to all non-zero values in reference_field_id. This is used to
+  // get more compact varint encoding.
+  optional uint64 reference_field_id_base = 6;
+
   // Indices for InternedData.field_names for the name of the field referring
-  // to the object.
+  // to the object. For Android S+ and for instances of normal classes (e.g.
+  // not instances of java.lang.Class or arrays), this is instead set in the
+  // corresponding HeapGraphType, and this is left empty.
   repeated uint64 reference_field_id = 4 [packed = true];
 
   // Ids of the Object that is referred to.
@@ -6535,12 +7181,7 @@
   // Types used in HeapGraphObjects.
   repeated HeapGraphType types = 9;
 
-  // Legacy way to encode types. Names here are emitted by old perfetto_hprof,
-  // which do not include the class_name. Handle like a HeapGraphType with
-  // no location_id.
-  // TODO(b/153552977): Remove this. This was was not used in any publicly
-  // available release.
-  repeated InternedString type_names = 3;
+  reserved 3;
 
   // Field names for references in managed heap graph.
   repeated InternedString field_names = 4;
@@ -6576,16 +7217,18 @@
     optional uint64 self_allocated = 2;
     // bytes allocated at this callstack that have been freed.
     optional uint64 self_freed = 3;
-    // bytes allocated at this callstack but not used since the last
-    // dump.
-    // See documentation of idle_allocations in HeapprofdConfig for more
-    // details.
-    optional uint64 self_idle = 7;
+    // deprecated self_idle.
+    reserved 7;
     // Bytes allocated by this callstack but not freed at the time the malloc
     // heap usage of this process was maximal. This is only set if dump_at_max
     // is true in HeapprofdConfig. In that case, self_allocated, self_freed and
     // self_idle will not be set.
     optional uint64 self_max = 8;
+    // Number of allocations that were sampled at this callstack but not freed
+    // at the time the malloc heap usage of this process was maximal. This is
+    // only set if dump_at_max is true in HeapprofdConfig. In that case,
+    // self_allocated, self_freed and self_idle will not be set.
+    optional uint64 self_max_count = 9;
     // timestamp [opt]
     optional uint64 timestamp = 4;
     // Number of allocations that were sampled at this callstack.
@@ -6617,6 +7260,7 @@
     optional uint64 map_reparses = 3;
     optional Histogram unwinding_time_us = 4;
     optional uint64 total_unwinding_time_us = 5;
+    optional uint64 client_spinlock_blocked_us = 6;
   }
 
   repeated ProcessHeapSamples process_dumps = 5;
@@ -6680,6 +7324,23 @@
   optional uint64 index = 7;
 }
 
+message StreamingAllocation {
+  // TODO(fmayer): Add callstack.
+  repeated uint64 address = 1;
+  repeated uint64 size = 2;
+  repeated uint64 sample_size = 3;
+  repeated uint64 clock_monotonic_coarse_timestamp = 4;
+  repeated uint32 heap_id = 5;
+  repeated uint64 sequence_number = 6;
+};
+
+message StreamingFree {
+  // TODO(fmayer): Add callstack.
+  repeated uint64 address = 1;
+  repeated uint32 heap_id = 2;
+  repeated uint64 sequence_number = 3;
+};
+
 // Message used to represent individual stack samples sampled at discrete
 // points in time, rather than aggregated over an interval.
 message StreamingProfilePacket {
@@ -6715,16 +7376,20 @@
     UNWIND_ERROR_MAX_FRAMES_EXCEEDED = 6;
     UNWIND_ERROR_REPEATED_FRAME = 7;
     UNWIND_ERROR_INVALID_ELF = 8;
+    UNWIND_ERROR_SYSTEM_CALL = 9;
+    UNWIND_ERROR_THREAD_TIMEOUT = 10;
+    UNWIND_ERROR_THREAD_DOES_NOT_EXIST = 11;
   }
 }
 
 // Individual performance sampling packet payload. Typically corresponds to a
 // stack sample on a configration-dependent counter overflow.
 // Timestamps are within the root packet (in the CLOCK_BOOTTIME domain).
-// There are three distinct views of this message:
+// There are several distinct views of this message:
 // * completely processed sample (callstack_iid set)
 // * indication of kernel buffer data loss (kernel_records_lost set)
 // * indication of skipped samples (sample_skipped_reason set)
+// * notable event in the sampling implementation (producer_event set)
 message PerfSample {
   optional uint32 cpu = 1;
   optional uint32 pid = 2;
@@ -6766,6 +7431,18 @@
   oneof optional_sample_skipped_reason {
     SampleSkipReason sample_skipped_reason = 18;
   };
+
+  // A notable event within the sampling implementation.
+  message ProducerEvent {
+    enum DataSourceStopReason {
+      PROFILER_STOP_UNKNOWN = 0;
+      PROFILER_STOP_GUARDRAIL = 1;
+    }
+    oneof optional_source_stop_reason {
+      DataSourceStopReason source_stop_reason = 1;
+    }
+  }
+  optional ProducerEvent producer_event = 19;
 }
 
 // End of protos/perfetto/trace/profiling/profile_packet.proto
@@ -6777,6 +7454,22 @@
   optional uint64 size_kb = 2;
   optional uint64 private_dirty_kb = 3;
   optional uint64 swap_kb = 4;
+
+  // for field upload (instead of path).
+  optional string file_name = 5;
+
+  // TODO(crbug.com/1098746): Consider encoding this as incremental values.
+  optional uint64 start_address = 6;
+  optional uint64 module_timestamp = 7;
+  optional string module_debugid = 8;
+  optional string module_debug_path = 9;
+  optional uint32 protection_flags = 10;
+
+  optional uint64 private_clean_resident_kb = 11;
+  optional uint64 shared_dirty_resident_kb = 12;
+  optional uint64 shared_clean_resident_kb = 13;
+  optional uint64 locked_kb = 14;
+  optional uint64 proportional_resident_kb = 15;
 };
 
 message SmapsPacket {
@@ -6832,6 +7525,17 @@
     optional int64 oom_score_adj = 10;
 
     repeated Thread threads = 11;
+
+    // The peak resident set size is resettable in newer Posix kernels.
+    // This field specifies if reset is supported and if the writer had reset
+    // the peaks after each process stats recording.
+    optional bool is_peak_rss_resettable = 12;
+
+    // Private, shared and swap footprint of the process as measured by
+    // Chrome. To know more about these metrics refer to:
+    // https://docs.google.com/document/d/1_WmgE1F5WUrhwkPqJis3dWyOiUmQKvpXp5cd4w86TvA
+    optional uint32 chrome_private_footprint_kb = 13;
+    optional uint32 chrome_peak_resident_set_kb = 14;
   }
   repeated Process processes = 1;
 
@@ -7037,6 +7741,21 @@
 
   // The current value of the random number sequence used in tests.
   optional uint32 seq_value = 2;
+
+  // Monotonically increased on each packet.
+  optional uint64 counter = 3;
+
+  // No more packets should follow (from the current sequence).
+  optional bool is_last = 4;
+
+  message TestPayload {
+    repeated string str = 1;
+    repeated TestPayload nested = 2;
+
+    // When 0 this is the bottom-most nested message.
+    optional uint32 remaining_nesting_depth = 3;
+  }
+  optional TestPayload payload = 5;
 }
 
 // End of protos/perfetto/trace/test_event.proto
@@ -7409,7 +8128,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 13 (up to 15).
-// Next id: 73.
+// Next id: 77.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -7442,6 +8161,8 @@
     FtraceStats ftrace_stats = 34;
     TraceStats trace_stats = 35;
     ProfilePacket profile_packet = 37;
+    StreamingAllocation streaming_allocation = 74;
+    StreamingFree streaming_free = 75;
     BatteryCounters battery = 38;
     PowerRails power_rails = 40;
     AndroidLogPacket android_log = 39;
@@ -7465,6 +8186,8 @@
     TracingServiceEvent service_event = 69;
     InitialDisplayState initial_display_state = 70;
     GpuMemTotalEvent gpu_mem_total_event = 71;
+    MemoryTrackerSnapshot memory_tracker_snapshot = 73;
+    FrameTimelineEvent frame_timeline_event = 76;
 
     // Only used in profile packets.
     ProfiledFrameSymbols profiled_frame_symbols = 55;
diff --git a/protos/perfetto/trace/profiling/BUILD.gn b/protos/perfetto/trace/profiling/BUILD.gn
index 3bf8905..3296bc1 100644
--- a/protos/perfetto/trace/profiling/BUILD.gn
+++ b/protos/perfetto/trace/profiling/BUILD.gn
@@ -16,6 +16,7 @@
 
 perfetto_proto_library("@TYPE@") {
   sources = [
+    "deobfuscation.proto",
     "heap_graph.proto",
     "profile_common.proto",
     "profile_packet.proto",
diff --git a/protos/perfetto/trace/profiling/deobfuscation.proto b/protos/perfetto/trace/profiling/deobfuscation.proto
new file mode 100644
index 0000000..94dde63
--- /dev/null
+++ b/protos/perfetto/trace/profiling/deobfuscation.proto
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+message ObfuscatedMember {
+  // This is the obfuscated field name relative to the class containing the
+  // ObfuscatedMember.
+  optional string obfuscated_name = 1;
+  // If this is fully qualified (i.e. contains a '.') this is the deobfuscated
+  // field name including its class. Otherwise, this is this the unqualified
+  // deobfuscated field name relative to the class containing this
+  // ObfuscatedMember.
+  optional string deobfuscated_name = 2;
+}
+
+message ObfuscatedClass {
+  optional string obfuscated_name = 1;
+  optional string deobfuscated_name = 2;
+  // fields.
+  repeated ObfuscatedMember obfuscated_members = 3;
+  repeated ObfuscatedMember obfuscated_methods = 4;
+}
+
+message DeobfuscationMapping {
+  optional string package_name = 1;
+  optional int64 version_code = 2;
+  repeated ObfuscatedClass obfuscated_classes = 3;
+}
\ No newline at end of file
diff --git a/protos/perfetto/trace/profiling/heap_graph.proto b/protos/perfetto/trace/profiling/heap_graph.proto
index eeb5bec..a0cb3fd 100644
--- a/protos/perfetto/trace/profiling/heap_graph.proto
+++ b/protos/perfetto/trace/profiling/heap_graph.proto
@@ -16,6 +16,9 @@
 
 syntax = "proto2";
 
+// TODO(fmayer): Remove this import once clients are migrated to the new
+// location.
+import public "protos/perfetto/trace/profiling/deobfuscation.proto";
 import "protos/perfetto/trace/profiling/profile_common.proto";
 
 // These messages encode a graph of objects that retain one another. Currently
@@ -23,29 +26,6 @@
 
 package perfetto.protos;
 
-message ObfuscatedMember {
-  // This is the obfuscated field name relative to the class containing the
-  // ObfuscatedMember.
-  optional string obfuscated_name = 1;
-  // If this is fully qualified (i.e. contains a '.') this is the deobfuscated
-  // field name including its class. Otherwise, this is this the unqualified
-  // deobfuscated field name relative to the class containing this
-  // ObfuscatedMember.
-  optional string deobfuscated_name = 2;
-}
-
-message ObfuscatedClass {
-  optional string obfuscated_name = 1;
-  optional string deobfuscated_name = 2;
-  repeated ObfuscatedMember obfuscated_members = 3;
-}
-
-message DeobfuscationMapping {
-  optional string package_name = 1;
-  optional int64 version_code = 2;
-  repeated ObfuscatedClass obfuscated_classes = 3;
-}
-
 message HeapGraphRoot {
   enum Type {
     ROOT_UNKNOWN = 0;
@@ -71,24 +51,55 @@
 }
 
 message HeapGraphType {
+  enum Kind {
+    KIND_UNKNOWN = 0;
+    KIND_NORMAL = 1;
+    KIND_NOREFERENCES = 2;
+    KIND_STRING = 3;
+    KIND_ARRAY = 4;
+    KIND_CLASS = 5;
+    KIND_CLASSLOADER = 6;
+    KIND_DEXCACHE = 7;
+    KIND_SOFT_REFERENCE = 8;
+    KIND_WEAK_REFERENCE = 9;
+    KIND_FINALIZER_REFERENCE = 10;
+    KIND_PHANTOM_REFERENCE = 11;
+  };
   // TODO(fmayer): Consider removing this and using the index in the repeaed
   // field to save space.
   optional uint64 id = 1;
   optional uint64 location_id = 2;
   optional string class_name = 3;
+  // Size of objects of this type.
+  optional uint64 object_size = 4;
+  optional uint64 superclass_id = 5;
+  // Indices for InternedData.field_names for the names of the fields of
+  // instances of this class. This does NOT include the fields from
+  // superclasses. The consumer of this data needs to walk all super
+  // classes to get a full lists of fields. Objects always write the
+  // fields in order of most specific class to the furthest up superclass.
+  repeated uint64 reference_field_id = 6 [packed = true];
+  optional Kind kind = 7;
+  optional uint64 classloader_id = 8;
 }
 
 message HeapGraphObject {
   optional uint64 id = 1;
 
-  // Index for InternedData.type_names for the name of the type of this object.
+  // Index for InternedData.types for the name of the type of this object.
   optional uint64 type_id = 2;
 
   // Bytes occupied by this objects.
   optional uint64 self_size = 3;
 
+  // Add this to all non-zero values in reference_field_id. This is used to
+  // get more compact varint encoding.
+  optional uint64 reference_field_id_base = 6;
+
   // Indices for InternedData.field_names for the name of the field referring
-  // to the object.
+  // to the object. For Android S+ and for instances of normal classes (e.g.
+  // not instances of java.lang.Class or arrays), this is instead set in the
+  // corresponding HeapGraphType, and this is left empty.
   repeated uint64 reference_field_id = 4 [packed = true];
 
   // Ids of the Object that is referred to.
@@ -114,12 +125,7 @@
   // Types used in HeapGraphObjects.
   repeated HeapGraphType types = 9;
 
-  // Legacy way to encode types. Names here are emitted by old perfetto_hprof,
-  // which do not include the class_name. Handle like a HeapGraphType with
-  // no location_id.
-  // TODO(b/153552977): Remove this. This was was not used in any publicly
-  // available release.
-  repeated InternedString type_names = 3;
+  reserved 3;
 
   // Field names for references in managed heap graph.
   repeated InternedString field_names = 4;
diff --git a/protos/perfetto/trace/profiling/profile_packet.proto b/protos/perfetto/trace/profiling/profile_packet.proto
index 1051776..ac68329 100644
--- a/protos/perfetto/trace/profiling/profile_packet.proto
+++ b/protos/perfetto/trace/profiling/profile_packet.proto
@@ -40,16 +40,18 @@
     optional uint64 self_allocated = 2;
     // bytes allocated at this callstack that have been freed.
     optional uint64 self_freed = 3;
-    // bytes allocated at this callstack but not used since the last
-    // dump.
-    // See documentation of idle_allocations in HeapprofdConfig for more
-    // details.
-    optional uint64 self_idle = 7;
+    // deprecated self_idle.
+    reserved 7;
     // Bytes allocated by this callstack but not freed at the time the malloc
     // heap usage of this process was maximal. This is only set if dump_at_max
     // is true in HeapprofdConfig. In that case, self_allocated, self_freed and
     // self_idle will not be set.
     optional uint64 self_max = 8;
+    // Number of allocations that were sampled at this callstack but not freed
+    // at the time the malloc heap usage of this process was maximal. This is
+    // only set if dump_at_max is true in HeapprofdConfig. In that case,
+    // self_allocated, self_freed and self_idle will not be set.
+    optional uint64 self_max_count = 9;
     // timestamp [opt]
     optional uint64 timestamp = 4;
     // Number of allocations that were sampled at this callstack.
@@ -81,6 +83,7 @@
     optional uint64 map_reparses = 3;
     optional Histogram unwinding_time_us = 4;
     optional uint64 total_unwinding_time_us = 5;
+    optional uint64 client_spinlock_blocked_us = 6;
   }
 
   repeated ProcessHeapSamples process_dumps = 5;
@@ -144,6 +147,23 @@
   optional uint64 index = 7;
 }
 
+message StreamingAllocation {
+  // TODO(fmayer): Add callstack.
+  repeated uint64 address = 1;
+  repeated uint64 size = 2;
+  repeated uint64 sample_size = 3;
+  repeated uint64 clock_monotonic_coarse_timestamp = 4;
+  repeated uint32 heap_id = 5;
+  repeated uint64 sequence_number = 6;
+};
+
+message StreamingFree {
+  // TODO(fmayer): Add callstack.
+  repeated uint64 address = 1;
+  repeated uint32 heap_id = 2;
+  repeated uint64 sequence_number = 3;
+};
+
 // Message used to represent individual stack samples sampled at discrete
 // points in time, rather than aggregated over an interval.
 message StreamingProfilePacket {
@@ -179,16 +199,20 @@
     UNWIND_ERROR_MAX_FRAMES_EXCEEDED = 6;
     UNWIND_ERROR_REPEATED_FRAME = 7;
     UNWIND_ERROR_INVALID_ELF = 8;
+    UNWIND_ERROR_SYSTEM_CALL = 9;
+    UNWIND_ERROR_THREAD_TIMEOUT = 10;
+    UNWIND_ERROR_THREAD_DOES_NOT_EXIST = 11;
   }
 }
 
 // Individual performance sampling packet payload. Typically corresponds to a
 // stack sample on a configration-dependent counter overflow.
 // Timestamps are within the root packet (in the CLOCK_BOOTTIME domain).
-// There are three distinct views of this message:
+// There are several distinct views of this message:
 // * completely processed sample (callstack_iid set)
 // * indication of kernel buffer data loss (kernel_records_lost set)
 // * indication of skipped samples (sample_skipped_reason set)
+// * notable event in the sampling implementation (producer_event set)
 message PerfSample {
   optional uint32 cpu = 1;
   optional uint32 pid = 2;
@@ -230,4 +254,16 @@
   oneof optional_sample_skipped_reason {
     SampleSkipReason sample_skipped_reason = 18;
   };
+
+  // A notable event within the sampling implementation.
+  message ProducerEvent {
+    enum DataSourceStopReason {
+      PROFILER_STOP_UNKNOWN = 0;
+      PROFILER_STOP_GUARDRAIL = 1;
+    }
+    oneof optional_source_stop_reason {
+      DataSourceStopReason source_stop_reason = 1;
+    }
+  }
+  optional ProducerEvent producer_event = 19;
 }
diff --git a/protos/perfetto/trace/profiling/smaps.proto b/protos/perfetto/trace/profiling/smaps.proto
index ec8ec2e..92521c3 100644
--- a/protos/perfetto/trace/profiling/smaps.proto
+++ b/protos/perfetto/trace/profiling/smaps.proto
@@ -23,6 +23,22 @@
   optional uint64 size_kb = 2;
   optional uint64 private_dirty_kb = 3;
   optional uint64 swap_kb = 4;
+
+  // for field upload (instead of path).
+  optional string file_name = 5;
+
+  // TODO(crbug.com/1098746): Consider encoding this as incremental values.
+  optional uint64 start_address = 6;
+  optional uint64 module_timestamp = 7;
+  optional string module_debugid = 8;
+  optional string module_debug_path = 9;
+  optional uint32 protection_flags = 10;
+
+  optional uint64 private_clean_resident_kb = 11;
+  optional uint64 shared_dirty_resident_kb = 12;
+  optional uint64 shared_clean_resident_kb = 13;
+  optional uint64 locked_kb = 14;
+  optional uint64 proportional_resident_kb = 15;
 };
 
 message SmapsPacket {
diff --git a/protos/perfetto/trace/ps/process_stats.proto b/protos/perfetto/trace/ps/process_stats.proto
index 9c1b1aa..df82c75 100644
--- a/protos/perfetto/trace/ps/process_stats.proto
+++ b/protos/perfetto/trace/ps/process_stats.proto
@@ -61,6 +61,17 @@
     optional int64 oom_score_adj = 10;
 
     repeated Thread threads = 11;
+
+    // The peak resident set size is resettable in newer Posix kernels.
+    // This field specifies if reset is supported and if the writer had reset
+    // the peaks after each process stats recording.
+    optional bool is_peak_rss_resettable = 12;
+
+    // Private, shared and swap footprint of the process as measured by
+    // Chrome. To know more about these metrics refer to:
+    // https://docs.google.com/document/d/1_WmgE1F5WUrhwkPqJis3dWyOiUmQKvpXp5cd4w86TvA
+    optional uint32 chrome_private_footprint_kb = 13;
+    optional uint32 chrome_peak_resident_set_kb = 14;
   }
   repeated Process processes = 1;
 
diff --git a/protos/perfetto/trace/test_event.proto b/protos/perfetto/trace/test_event.proto
index 7a6902b..c55b1a5 100644
--- a/protos/perfetto/trace/test_event.proto
+++ b/protos/perfetto/trace/test_event.proto
@@ -25,4 +25,19 @@
 
   // The current value of the random number sequence used in tests.
   optional uint32 seq_value = 2;
+
+  // Monotonically increased on each packet.
+  optional uint64 counter = 3;
+
+  // No more packets should follow (from the current sequence).
+  optional bool is_last = 4;
+
+  message TestPayload {
+    repeated string str = 1;
+    repeated TestPayload nested = 2;
+
+    // When 0 this is the bottom-most nested message.
+    optional uint32 remaining_nesting_depth = 3;
+  }
+  optional TestPayload payload = 5;
 }
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index 431ad4f..667c62a 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -20,6 +20,7 @@
 import "protos/perfetto/config/trace_config.proto";
 import "protos/perfetto/trace/extension_descriptor.proto";
 import "protos/perfetto/trace/android/android_log.proto";
+import "protos/perfetto/trace/android/frame_timeline_event.proto";
 import "protos/perfetto/trace/android/gpu_mem_event.proto";
 import "protos/perfetto/trace/android/graphics_frame_event.proto";
 import "protos/perfetto/trace/android/initial_display_state.proto";
@@ -37,10 +38,12 @@
 import "protos/perfetto/trace/gpu/vulkan_memory_event.proto";
 import "protos/perfetto/trace/gpu/vulkan_api_event.proto";
 import "protos/perfetto/trace/interned_data/interned_data.proto";
+import "protos/perfetto/trace/memory_graph.proto";
 import "protos/perfetto/trace/perfetto/perfetto_metatrace.proto";
 import "protos/perfetto/trace/perfetto/tracing_service_event.proto";
 import "protos/perfetto/trace/power/battery_counters.proto";
 import "protos/perfetto/trace/power/power_rails.proto";
+import "protos/perfetto/trace/profiling/deobfuscation.proto";
 import "protos/perfetto/trace/profiling/heap_graph.proto";
 import "protos/perfetto/trace/profiling/profile_common.proto";
 import "protos/perfetto/trace/profiling/profile_packet.proto";
@@ -80,7 +83,7 @@
 // See the [Buffers and Dataflow](/docs/concepts/buffers.md) doc for details.
 //
 // Next reserved id: 13 (up to 15).
-// Next id: 73.
+// Next id: 77.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -113,6 +116,8 @@
     FtraceStats ftrace_stats = 34;
     TraceStats trace_stats = 35;
     ProfilePacket profile_packet = 37;
+    StreamingAllocation streaming_allocation = 74;
+    StreamingFree streaming_free = 75;
     BatteryCounters battery = 38;
     PowerRails power_rails = 40;
     AndroidLogPacket android_log = 39;
@@ -136,6 +141,8 @@
     TracingServiceEvent service_event = 69;
     InitialDisplayState initial_display_state = 70;
     GpuMemTotalEvent gpu_mem_total_event = 71;
+    MemoryTrackerSnapshot memory_tracker_snapshot = 73;
+    FrameTimelineEvent frame_timeline_event = 76;
 
     // Only used in profile packets.
     ProfiledFrameSymbols profiled_frame_symbols = 55;
diff --git a/protos/perfetto/trace/track_event/BUILD.gn b/protos/perfetto/trace/track_event/BUILD.gn
index c0e5291..22c329d 100644
--- a/protos/perfetto/trace/track_event/BUILD.gn
+++ b/protos/perfetto/trace/track_event/BUILD.gn
@@ -16,15 +16,20 @@
 
 perfetto_proto_library("@TYPE@") {
   sources = [
+    "chrome_application_state_info.proto",
     "chrome_compositor_scheduler_state.proto",
     "chrome_frame_reporter.proto",
     "chrome_histogram_sample.proto",
     "chrome_keyed_service.proto",
     "chrome_latency_info.proto",
     "chrome_legacy_ipc.proto",
+    "chrome_message_pump.proto",
+    "chrome_mojo_event_info.proto",
     "chrome_process_descriptor.proto",
+    "chrome_renderer_scheduler_state.proto",
     "chrome_thread_descriptor.proto",
     "chrome_user_event.proto",
+    "chrome_window_handle_event_info.proto",
     "counter_descriptor.proto",
     "debug_annotation.proto",
     "log_message.proto",
diff --git a/protos/perfetto/trace/track_event/chrome_application_state_info.proto b/protos/perfetto/trace/track_event/chrome_application_state_info.proto
new file mode 100644
index 0000000..f5884a2
--- /dev/null
+++ b/protos/perfetto/trace/track_event/chrome_application_state_info.proto
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+
+// Trace event arguments for application state changes.
+message ChromeApplicationStateInfo {
+  // Enum definition taken from:
+  // https://source.chromium.org/chromium/chromium/src/+/master:base/android/application_status_listener.h
+  enum ChromeApplicationState {
+    APPLICATION_STATE_UNKNOWN = 0;
+    APPLICATION_STATE_HAS_RUNNING_ACTIVITIES = 1;
+    APPLICATION_STATE_HAS_PAUSED_ACTIVITIES = 2;
+    APPLICATION_STATE_HAS_STOPPED_ACTIVITIES = 3;
+    APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES = 4;
+  };
+  optional ChromeApplicationState application_state = 1;
+}
diff --git a/protos/perfetto/trace/track_event/chrome_frame_reporter.proto b/protos/perfetto/trace/track_event/chrome_frame_reporter.proto
index d1c24ed..16d8d42 100644
--- a/protos/perfetto/trace/track_event/chrome_frame_reporter.proto
+++ b/protos/perfetto/trace/track_event/chrome_frame_reporter.proto
@@ -63,4 +63,9 @@
 
   optional uint64 frame_source = 3;
   optional uint64 frame_sequence = 4;
+
+  // If this is a droped frame (i.e. if |state| is set to |STATE_DROPPED| or
+  // |STATE_PRESENTED_PARTIAL|), then indicates whether this frame impacts
+  // smoothness.
+  optional bool affects_smoothness = 5;
 }
diff --git a/protos/perfetto/trace/track_event/chrome_histogram_sample.proto b/protos/perfetto/trace/track_event/chrome_histogram_sample.proto
index 67dd28c..d326b62 100644
--- a/protos/perfetto/trace/track_event/chrome_histogram_sample.proto
+++ b/protos/perfetto/trace/track_event/chrome_histogram_sample.proto
@@ -18,11 +18,18 @@
 
 package perfetto.protos;
 
+message HistogramName {
+  optional uint64 iid = 1;
+  optional string name = 2;
+}
+
 // An individual histogram sample logged via Chrome's UMA metrics system.
 message ChromeHistogramSample {
-  // MD5 hash of the metric name. Either |name_hash| or |name| or both
-  // must be present.
+  // MD5 hash of the metric name. Either |name_hash| or |name|/|name_iid| or
+  // both must be present.
   optional uint64 name_hash = 1;
   optional string name = 2;
   optional int64 sample = 3;
+  // Interned HistogramName. Only one of |name|, |name_iid| can be set.
+  optional uint64 name_iid = 4;
 }
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/protos/perfetto/trace/track_event/chrome_message_pump.proto
similarity index 69%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to protos/perfetto/trace/track_event/chrome_message_pump.proto
index e22189e..56bf50a 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/protos/perfetto/trace/track_event/chrome_message_pump.proto
@@ -18,10 +18,8 @@
 
 package perfetto.protos;
 
-import "protos/perfetto/metrics/custom_options.proto";
-
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+// Details about Chrome message pump events
+message ChromeMessagePump {
+  // True if there are sent messages in the queue.
+  optional bool sent_messages_in_queue = 1;
 }
diff --git a/protos/perfetto/trace/track_event/chrome_mojo_event_info.proto b/protos/perfetto/trace/track_event/chrome_mojo_event_info.proto
new file mode 100644
index 0000000..3d7ac23
--- /dev/null
+++ b/protos/perfetto/trace/track_event/chrome_mojo_event_info.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Contains information to identify mojo handling events. The trace events in
+// mojo are common for all mojo interfaces and this information is used to
+// identify who is the caller or callee.
+message ChromeMojoEventInfo {
+  // Contains the interface name or the file name of the creator of a mojo
+  // handle watcher, recorded when an event if notified to the watcher. The code
+  // that runs within the track event belongs to the interface.
+  optional string watcher_notify_interface_tag = 1;
+}
diff --git a/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto b/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto
new file mode 100644
index 0000000..ade7897
--- /dev/null
+++ b/protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// Describes the state of the RendererScheduler for a given Renderer Process.
+
+// RAIL Mode is an indication of the kind of work that a Renderer is currently
+// performing which is in turn used to prioritise work accordingly.
+// A fuller description of these modes can be found https://web.dev/rail/
+enum ChromeRAILMode {
+  RAIL_MODE_NONE = 0;
+  RAIL_MODE_RESPONSE = 1;
+  RAIL_MODE_ANIMATION = 2;
+  RAIL_MODE_IDLE = 3;
+  RAIL_MODE_LOAD = 4;
+}
+
+// Next id: 2
+message ChromeRendererSchedulerState {
+  optional ChromeRAILMode rail_mode = 1;
+}
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
similarity index 69%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
index e22189e..8143537 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto
@@ -18,10 +18,8 @@
 
 package perfetto.protos;
 
-import "protos/perfetto/metrics/custom_options.proto";
-
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+// Details about HWNDMessageHandler trace events.
+message ChromeWindowHandleEventInfo {
+  optional uint32 dpi = 1;
+  optional uint32 message_id = 2;
 }
diff --git a/protos/perfetto/trace/track_event/track_event.proto b/protos/perfetto/trace/track_event/track_event.proto
index ee0db5f..aa32dcc 100644
--- a/protos/perfetto/trace/track_event/track_event.proto
+++ b/protos/perfetto/trace/track_event/track_event.proto
@@ -19,13 +19,19 @@
 import "protos/perfetto/trace/track_event/debug_annotation.proto";
 import "protos/perfetto/trace/track_event/log_message.proto";
 import "protos/perfetto/trace/track_event/task_execution.proto";
+import "protos/perfetto/trace/track_event/chrome_application_state_info.proto";
 import "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto";
 import "protos/perfetto/trace/track_event/chrome_frame_reporter.proto";
 import "protos/perfetto/trace/track_event/chrome_histogram_sample.proto";
 import "protos/perfetto/trace/track_event/chrome_keyed_service.proto";
 import "protos/perfetto/trace/track_event/chrome_latency_info.proto";
 import "protos/perfetto/trace/track_event/chrome_legacy_ipc.proto";
+import "protos/perfetto/trace/track_event/chrome_message_pump.proto";
+import "protos/perfetto/trace/track_event/chrome_mojo_event_info.proto";
+import "protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto";
 import "protos/perfetto/trace/track_event/chrome_user_event.proto";
+import "protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto";
+import "protos/perfetto/trace/track_event/source_location.proto";
 
 package perfetto.protos;
 
@@ -38,6 +44,7 @@
 // referred to via the track's UUID.
 //
 // A simple TrackEvent packet specifies a timestamp, category, name and type:
+// ```protobuf
 //   trace_packet {
 //     timestamp: 1000
 //     track_event {
@@ -46,9 +53,11 @@
 //       type: TYPE_INSTANT
 //      }
 //    }
+// ```
 //
 // To associate an event with a custom track (e.g. a thread), the track is
 // defined in a separate packet and referred to from the TrackEvent by its UUID:
+// ```protobuf
 //   trace_packet {
 //     track_descriptor {
 //       track_uuid: 1234
@@ -62,8 +71,11 @@
 //       }
 //     }
 //   }
+// ```
 //
 // A pair of TYPE_SLICE_BEGIN and _END events form a slice on the track:
+//
+// ```protobuf
 //   trace_packet {
 //     timestamp: 1200
 //     track_event {
@@ -80,7 +92,7 @@
 //       type: TYPE_SLICE_END
 //     }
 //   }
-//
+// ```
 // TrackEvents also support optimizations to reduce data repetition and encoded
 // data size, e.g. through data interning (names, categories, ...) and delta
 // encoding of timestamps/counters. For details, see the InternedData message.
@@ -88,7 +100,7 @@
 // their default track association) can be emitted as part of a
 // TrackEventDefaults message.
 //
-// Next reserved id: 13 (up to 15). Next id: 33.
+// Next reserved id: 13 (up to 15). Next id: 43.
 message TrackEvent {
   // Names of categories of the event. In the client library, categories are a
   // way to turn groups of individual events on or off.
@@ -179,7 +191,27 @@
   repeated uint64 extra_counter_track_uuids = 31;
   repeated int64 extra_counter_values = 12;
 
-  // TODO(eseckler): Add flow event support.
+  // IDs of flows originating, passing through, or ending at this event.
+  // Flow IDs are global within a trace.
+  //
+  // A flow connects a sequence of TrackEvents within or across tracks, e.g.
+  // an input event may be handled on one thread but cause another event on
+  // a different thread - a flow between the two events can associate them.
+  //
+  // The direction of the flows between events is inferred from the events'
+  // timestamps. The earliest event with the same flow ID becomes the source
+  // of the flow. Any events thereafter are intermediate steps of the flow,
+  // until the flow terminates at the last event with the flow ID.
+  //
+  // Flows can also be explicitly terminated (see |terminating_flow_ids|), so
+  // that the same ID can later be reused for another flow.
+  repeated uint64 flow_ids = 36;
+
+  // List of flow ids which should terminate on this event, otherwise same as
+  // |flow_ids|.
+  // Any one flow ID should be either listed as part of |flow_ids| OR
+  // |terminating_flow_ids|, not both.
+  repeated uint64 terminating_flow_ids = 42;
 
   // ---------------------------------------------------------------------------
   // TrackEvent arguments:
@@ -199,6 +231,24 @@
   optional ChromeHistogramSample chrome_histogram_sample = 28;
   optional ChromeLatencyInfo chrome_latency_info = 29;
   optional ChromeFrameReporter chrome_frame_reporter = 32;
+  optional ChromeApplicationStateInfo chrome_application_state_info = 39;
+  optional ChromeRendererSchedulerState chrome_renderer_scheduler_state = 40;
+  optional ChromeWindowHandleEventInfo chrome_window_handle_event_info = 41;
+
+  // This field is used only if the source location represents the function that
+  // executes during this event.
+  oneof source_location_field {
+    // Non-interned field.
+    SourceLocation source_location = 33;
+    // TODO(ssid): The interned source locations are not parsed by trace
+    // processor.
+    // Interned field.
+    uint64 source_location_iid = 34;
+  }
+
+  optional ChromeMessagePump chrome_message_pump = 35;
+  optional ChromeMojoEventInfo chrome_mojo_event_info = 38;
+
   // New argument types go here :)
 
   // Extension range for typed events defined externally.
diff --git a/protos/perfetto/trace_processor/BUILD.gn b/protos/perfetto/trace_processor/BUILD.gn
index 461fd9c..ee1c795 100644
--- a/protos/perfetto/trace_processor/BUILD.gn
+++ b/protos/perfetto/trace_processor/BUILD.gn
@@ -17,6 +17,7 @@
 
 perfetto_proto_library("@TYPE@") {
   proto_generators = [ "zero" ]
+  deps = [ "../common:zero" ]  # ../common:zero needed for descriptor.proto.
   sources = []
   foreach(source, trace_processor_protos) {
     sources += [ "$source.proto" ]
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 2517050..f32745b 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -18,6 +18,8 @@
 
 package perfetto.protos;
 
+import "protos/perfetto/common/descriptor.proto";
+
 // This file defines the schema for {,un}marshalling arguments and return values
 // when interfacing to the trace processor binary interface.
 
@@ -140,17 +142,27 @@
 
 // Input for the /compute_metric endpoint.
 message ComputeMetricArgs {
+  enum ResultFormat {
+    BINARY_PROTOBUF = 0;
+    TEXTPROTO = 1;
+  }
   repeated string metric_names = 1;
+  optional ResultFormat format = 2;
 }
 
 // Output for the /compute_metric endpoint.
 message ComputeMetricResult {
-  // This is meant to contain a perfetto.protos.TraceMetrics. We're using bytes
-  // instead of the actual type because we do not want to generate protozero
-  // code for the metrics protos. We always encode/decode metrics using a
-  // reflection based mechanism that does not require the compiled C++ code.
-  // This allows us to read in new protos at runtime.
-  optional bytes metrics = 1;
+  oneof result {
+    // This is meant to contain a perfetto.protos.TraceMetrics. We're using
+    // bytes instead of the actual type because we do not want to generate
+    // protozero code for the metrics protos. We always encode/decode metrics
+    // using a reflection based mechanism that does not require the compiled C++
+    // code. This allows us to read in new protos at runtime.
+    bytes metrics = 1;
+
+    // A perfetto.protos.TraceMetrics formatted as prototext.
+    string metrics_as_prototext = 3;
+  }
 
   optional string error = 2;
 }
@@ -171,3 +183,17 @@
   optional bytes metatrace = 1;
   optional string error = 2;
 }
+
+// Convenience wrapper for multiple descriptors, similar to FileDescriptorSet
+// in descriptor.proto.
+message DescriptorSet {
+  repeated DescriptorProto descriptors = 1;
+}
+
+// Input for the /get_metric_descriptors endpoint.
+message GetMetricDescriptorsArgs {}
+
+// Output for the /get_metric_descriptors endpoint.
+message GetMetricDescriptorsResult {
+  optional DescriptorSet descriptor_set = 1;
+}
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
new file mode 100644
index 0000000..31cd53a
--- /dev/null
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+import "third_party/perfetto/protos/perfetto/trace/track_event/track_event.proto";
+
+package perfetto.protos;
+
+enum ChromeAppState {
+  APP_STATE_FOREGROUND = 1;
+  APP_STATE_BACKGROUND = 2;
+}
+
+message ChromeTrackEvent {
+  // Extension range for Chrome: 1000-1099
+  extend TrackEvent { optional ChromeAppState chrome_app_state = 1000; }
+}
diff --git a/src/android_internal/BUILD.gn b/src/android_internal/BUILD.gn
index 956e07c..9f50f7f 100644
--- a/src/android_internal/BUILD.gn
+++ b/src/android_internal/BUILD.gn
@@ -26,11 +26,10 @@
 source_set("headers") {
   deps = [
     "../../gn:default_deps",
-    "../../src/perfetto_cmd:perfetto_atoms",
+    "../android_stats:perfetto_atoms",
   ]
   sources = [
     "atrace_hal.h",
-    "dropbox_service.h",
     "health_hal.h",
     "incident_service.h",
     "power_stats_hal.h",
@@ -62,7 +61,6 @@
   if (perfetto_build_with_android) {
     sources = [
       "atrace_hal.cc",
-      "dropbox_service.cc",
       "health_hal.cc",
       "incident_service.cc",
       "power_stats_hal.cc",
diff --git a/src/android_internal/dropbox_service.cc b/src/android_internal/dropbox_service.cc
deleted file mode 100644
index 8088028..0000000
--- a/src/android_internal/dropbox_service.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/android_internal/dropbox_service.h"
-
-#include <android/os/DropBoxManager.h>
-#include <utils/StrongPointer.h>
-
-namespace perfetto {
-namespace android_internal {
-
-bool SaveIntoDropbox(const char* tag, int fd) {
-  using android::os::DropBoxManager;
-  android::sp<DropBoxManager> dropbox(new DropBoxManager());
-  auto status = dropbox->addFile(android::String16(tag), fd, /*flags=*/0);
-  return status.isOk();
-}
-
-}  // namespace android_internal
-}  // namespace perfetto
diff --git a/src/android_internal/dropbox_service.h b/src/android_internal/dropbox_service.h
deleted file mode 100644
index 6365355..0000000
--- a/src/android_internal/dropbox_service.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_ANDROID_INTERNAL_DROPBOX_SERVICE_H_
-#define SRC_ANDROID_INTERNAL_DROPBOX_SERVICE_H_
-
-namespace perfetto {
-namespace android_internal {
-
-extern "C" {
-
-// Saves the passed file into Dropbox with the given tag.
-// Takes ownership of the passed file descriptor.
-bool __attribute__((visibility("default")))
-SaveIntoDropbox(const char* tag, int fd);
-
-}  // extern "C"
-
-}  // namespace android_internal
-}  // namespace perfetto
-
-#endif  // SRC_ANDROID_INTERNAL_DROPBOX_SERVICE_H_
diff --git a/src/android_internal/incident_service.cc b/src/android_internal/incident_service.cc
index 10a0f81..5e23afb 100644
--- a/src/android_internal/incident_service.cc
+++ b/src/android_internal/incident_service.cc
@@ -16,11 +16,10 @@
 
 #include "src/android_internal/incident_service.h"
 
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
 #include <binder/Status.h>
+#include <incident/incident_report.h>
 #include <stddef.h>
 #include <stdint.h>
 
@@ -31,34 +30,27 @@
 
 bool StartIncidentReport(const char* dest_pkg,
                          const char* dest_class,
-                         int privacy_level) {
-  android::os::IncidentReportArgs incidentReport;
-  incidentReport.addSection(3026);  // system_trace only
-
-  if (privacy_level != android::os::PRIVACY_POLICY_AUTOMATIC &&
-      privacy_level != android::os::PRIVACY_POLICY_EXPLICIT) {
+                         int privacy_policy) {
+  if (privacy_policy != INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC &&
+      privacy_policy != INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT) {
     return false;
   }
-  incidentReport.setPrivacyPolicy(privacy_level);
 
-  std::string pkg(dest_pkg);
-  std::string cls(dest_class);
-  if (pkg.size() == 0 || cls.size() == 0) {
+  if (strlen(dest_pkg) == 0 || strlen(dest_class) == 0) {
     return false;
   }
-  incidentReport.setReceiverPkg(pkg);
-  incidentReport.setReceiverCls(cls);
 
-  android::sp<android::os::IIncidentManager> service =
-      android::interface_cast<android::os::IIncidentManager>(
-          android::defaultServiceManager()->getService(
-              android::String16("incident")));
+  AIncidentReportArgs* args = AIncidentReportArgs_init();
 
-  if (!service) {
-    return false;
-  }
-  android::binder::Status s = service->reportIncident(incidentReport);
-  return s.isOk();
+  AIncidentReportArgs_addSection(args, 3026);  // system_trace only
+  AIncidentReportArgs_setPrivacyPolicy(args, privacy_policy);
+  AIncidentReportArgs_setReceiverPackage(args, dest_pkg);
+  AIncidentReportArgs_setReceiverClass(args, dest_class);
+
+  int err = AIncidentReportArgs_takeReport(args);
+  AIncidentReportArgs_delete(args);
+
+  return err == 0;
 }
 
 }  // namespace android_internal
diff --git a/src/android_internal/statsd_logging.cc b/src/android_internal/statsd_logging.cc
index aa7b24b..96a6333 100644
--- a/src/android_internal/statsd_logging.cc
+++ b/src/android_internal/statsd_logging.cc
@@ -23,11 +23,15 @@
 namespace perfetto {
 namespace android_internal {
 
-void StatsdLogEvent(PerfettoStatsdAtom atom,
-                    int64_t uuid_lsb,
-                    int64_t uuid_msb) {
-  stats_write(PERFETTO_UPLOADED, static_cast<int32_t>(atom), uuid_lsb,
-              uuid_msb);
+void StatsdLogUploadEvent(PerfettoStatsdAtom atom,
+                          int64_t uuid_lsb,
+                          int64_t uuid_msb) {
+  stats_write(PERFETTO_UPLOADED, static_cast<int32_t>(atom), uuid_lsb, uuid_msb,
+              "");
+}
+
+void StatsdLogTriggerEvent(PerfettoTriggerAtom atom, const char* trigger_name) {
+  stats_write(PERFETTO_TRIGGER, static_cast<int32_t>(atom), trigger_name);
 }
 
 }  // namespace android_internal
diff --git a/src/android_internal/statsd_logging.h b/src/android_internal/statsd_logging.h
index e1f9c76..fc65f21 100644
--- a/src/android_internal/statsd_logging.h
+++ b/src/android_internal/statsd_logging.h
@@ -20,7 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "src/perfetto_cmd/perfetto_atoms.h"
+#include "src/android_stats/perfetto_atoms.h"
 
 namespace perfetto {
 namespace android_internal {
@@ -28,7 +28,12 @@
 extern "C" {
 
 void __attribute__((visibility("default")))
-StatsdLogEvent(PerfettoStatsdAtom atom, int64_t uuid_lsb, int64_t uuid_msb);
+StatsdLogUploadEvent(PerfettoStatsdAtom atom,
+                     int64_t uuid_lsb,
+                     int64_t uuid_msb);
+
+void __attribute__((visibility("default")))
+StatsdLogTriggerEvent(PerfettoTriggerAtom atom, const char* trigger_name);
 
 }  // extern "C"
 
diff --git a/src/android_stats/BUILD.gn b/src/android_stats/BUILD.gn
new file mode 100644
index 0000000..fcf923e
--- /dev/null
+++ b/src/android_stats/BUILD.gn
@@ -0,0 +1,34 @@
+# 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.
+
+import("../../gn/perfetto.gni")
+
+source_set("perfetto_atoms") {
+  sources = [ "perfetto_atoms.h" ]
+  deps = [ "../../gn:default_deps" ]
+}
+
+source_set("android_stats") {
+  deps = [
+    "../../gn:default_deps",
+    "../../include/perfetto/base",
+    "../android_internal:headers",
+    "../android_internal:lazy_library_loader",
+  ]
+  public_deps = [ ":perfetto_atoms" ]
+  sources = [
+    "statsd_logging_helper.cc",
+    "statsd_logging_helper.h",
+  ]
+}
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
new file mode 100644
index 0000000..d1d09ef
--- /dev/null
+++ b/src/android_stats/perfetto_atoms.h
@@ -0,0 +1,116 @@
+/*
+ * 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_ANDROID_STATS_PERFETTO_ATOMS_H_
+#define SRC_ANDROID_STATS_PERFETTO_ATOMS_H_
+
+namespace perfetto {
+
+// This must match the values of the PerfettoUploadEvent enum in:
+// frameworks/proto_logging/stats/atoms.proto
+enum class PerfettoStatsdAtom {
+  kUndefined = 0,
+
+  // Checkpoints inside perfetto_cmd before tracing is finished.
+  kTraceBegin = 1,
+  kBackgroundTraceBegin = 2,
+  kOnConnect = 3,
+
+  // Guardrails inside perfetto_cmd before tracing is finished.
+  kOnTimeout = 16,
+  kCmdUserBuildTracingNotAllowed = 43,
+  kCmdFailedToInitGuardrailState = 44,
+  kCmdInvalidGuardrailState = 45,
+  kCmdHitUploadLimit = 46,
+
+  // Checkpoints inside traced.
+  kTracedEnableTracing = 37,
+  kTracedStartTracing = 38,
+  kTracedDisableTracing = 39,
+  kTracedNotifyTracingDisabled = 40,
+
+  // Trigger checkpoints inside traced.
+  // These atoms are special because, along with the UUID,
+  // they log the trigger name.
+  kTracedTriggerStartTracing = 41,
+  kTracedTriggerStopTracing = 42,
+
+  // Guardrails inside traced.
+  kTracedEnableTracingExistingTraceSession = 18,
+  kTracedEnableTracingTooLongTrace = 19,
+  kTracedEnableTracingInvalidTriggerTimeout = 20,
+  kTracedEnableTracingDurationWithTrigger = 21,
+  kTracedEnableTracingStopTracingWriteIntoFile = 22,
+  kTracedEnableTracingDuplicateTriggerName = 23,
+  kTracedEnableTracingInvalidDeferredStart = 24,
+  kTracedEnableTracingInvalidBufferSize = 25,
+  kTracedEnableTracingBufferSizeTooLarge = 26,
+  kTracedEnableTracingTooManyBuffers = 27,
+  kTracedEnableTracingDuplicateSessionName = 28,
+  kTracedEnableTracingSessionNameTooRecent = 29,
+  kTracedEnableTracingTooManySessionsForUid = 30,
+  kTracedEnableTracingTooManyConcurrentSessions = 31,
+  kTracedEnableTracingInvalidFdOutputFile = 32,
+  kTracedEnableTracingFailedToCreateFile = 33,
+  kTracedEnableTracingOom = 34,
+  kTracedEnableTracingUnknown = 35,
+  kTracedStartTracingInvalidSessionState = 36,
+
+  // Checkpoints inside perfetto_cmd after tracing has finished.
+  kOnTracingDisabled = 4,
+  kUploadIncidentBegin = 8,
+  kFinalizeTraceAndExit = 11,
+  kNotUploadingEmptyTrace = 17,
+
+  // Guardrails inside perfetto_cmd after tracing has finished.
+  kUploadIncidentFailure = 10,
+
+  // Deprecated as "success" is misleading; it simply means we were
+  // able to communicate with incidentd. Will be removed once
+  // incidentd is properly instrumented.
+  kUploadIncidentSuccess = 9,
+
+  // Deprecated as has the potential to be too spammy. Will be
+  // replaced with a whole new atom proto which uses a count metric
+  // instead of the event metric used for this proto.
+  kTriggerBegin = 12,
+  kTriggerSuccess = 13,
+  kTriggerFailure = 14,
+
+  // Deprecated as too coarse grained to be useful. Will be replaced
+  // with better broken down atoms as we do with traced.
+  kHitGuardrails = 15,
+
+  // Contained status of Dropbox uploads. Removed as Perfetto no
+  // longer supports uploading traces using Dropbox.
+  // reserved 5, 6, 7;
+};
+
+// This must match the values of the PerfettoTrigger::TriggerType enum in:
+// frameworks/base/cmds/statsd/src/atoms.proto
+enum PerfettoTriggerAtom {
+  kUndefined = 0,
+
+  kCmdTrigger = 1,
+  kCmdTriggerFail = 2,
+
+  kTriggerPerfettoTrigger = 3,
+  kTriggerPerfettoTriggerFail = 4,
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_ANDROID_STATS_PERFETTO_ATOMS_H_
diff --git a/src/android_stats/statsd_logging_helper.cc b/src/android_stats/statsd_logging_helper.cc
new file mode 100644
index 0000000..2afb534
--- /dev/null
+++ b/src/android_stats/statsd_logging_helper.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/android_stats/statsd_logging_helper.h"
+
+#include <string>
+
+#include "perfetto/base/compiler.h"
+#include "src/android_internal/lazy_library_loader.h"
+#include "src/android_internal/statsd_logging.h"
+
+namespace perfetto {
+namespace android_stats {
+namespace {
+// |g_android_logging_enabled| is one mechanism to make
+// sure we don't accidentally log on non-Android tree
+// platforms. The other is that PERFETTO_LAZY_LOAD will
+// return a nullptr on all non-Android in-tree platforms
+// as libperfetto_android_internal will not be available.
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+constexpr bool g_android_logging_enabled = true;
+#else
+constexpr bool g_android_logging_enabled = false;
+#endif
+}  // namespace
+
+void MaybeLogUploadEvent(PerfettoStatsdAtom atom,
+                         int64_t uuid_lsb,
+                         int64_t uuid_msb) {
+  if (!g_android_logging_enabled)
+    return;
+
+  PERFETTO_LAZY_LOAD(android_internal::StatsdLogUploadEvent, log_event_fn);
+  if (log_event_fn) {
+    log_event_fn(atom, uuid_lsb, uuid_msb);
+  }
+}
+
+void MaybeLogTriggerEvents(PerfettoTriggerAtom atom,
+                           const std::vector<std::string>& triggers) {
+  if (!g_android_logging_enabled)
+    return;
+
+  PERFETTO_LAZY_LOAD(android_internal::StatsdLogTriggerEvent, log_event_fn);
+  if (log_event_fn) {
+    for (const std::string& trigger_name : triggers) {
+      log_event_fn(atom, trigger_name.c_str());
+    }
+  }
+}
+
+}  // namespace android_stats
+}  // namespace perfetto
diff --git a/src/android_stats/statsd_logging_helper.h b/src/android_stats/statsd_logging_helper.h
new file mode 100644
index 0000000..57ac9cc
--- /dev/null
+++ b/src/android_stats/statsd_logging_helper.h
@@ -0,0 +1,45 @@
+/*
+ * 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_ANDROID_STATS_STATSD_LOGGING_HELPER_H_
+#define SRC_ANDROID_STATS_STATSD_LOGGING_HELPER_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "src/android_stats/perfetto_atoms.h"
+
+namespace perfetto {
+namespace android_stats {
+
+// Functions in this file are only active on built in the Android
+// tree. On other platforms (including Android standalone and Chromium
+// on Android) these functions are a noop.
+
+// Logs the upload event to statsd if built in the Android tree.
+void MaybeLogUploadEvent(PerfettoStatsdAtom atom,
+                         int64_t uuid_lsb,
+                         int64_t uuid_msb);
+
+// Logs the trigger events to statsd if built in the Android tree.
+void MaybeLogTriggerEvents(PerfettoTriggerAtom atom,
+                           const std::vector<std::string>& triggers);
+
+}  // namespace android_stats
+}  // namespace perfetto
+
+#endif  // SRC_ANDROID_STATS_STATSD_LOGGING_HELPER_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 055d70d..d638f09 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -13,28 +13,36 @@
 # limitations under the License.
 
 import("//build_overrides/build.gni")
+import("../../gn/gen_perfetto_version_header.gni")
 import("../../gn/perfetto.gni")
+import("../../gn/perfetto_component.gni")
 import("../../gn/test.gni")
 import("../../gn/wasm.gni")
 
-source_set("base") {
+perfetto_component("base") {
   deps = [ "../../gn:default_deps" ]
   public_deps = [
     "../../include/perfetto/base",
     "../../include/perfetto/ext/base",
   ]
   sources = [
+    "event_fd.cc",
     "file_utils.cc",
     "logging.cc",
     "metatrace.cc",
     "paged_memory.cc",
+    "pipe.cc",
+    "status.cc",
     "string_splitter.cc",
     "string_utils.cc",
     "string_view.cc",
-    "subprocess.cc",
+    "subprocess_posix.cc",
+    "temp_file.cc",
     "thread_checker.cc",
     "time.cc",
+    "utils.cc",
     "uuid.cc",
+    "version.cc",
     "virtual_destructors.cc",
     "waitable_event.cc",
     "watchdog_posix.cc",
@@ -42,18 +50,33 @@
 
   # TODO(brucedawson): Enable these for Windows when possible.
   if (!is_win && !is_nacl) {
-    sources += [
-      "event_fd.cc",
-      "pipe.cc",
-      "temp_file.cc",
-      "thread_task_runner.cc",
-      "unix_task_runner.cc",
-    ]
+    sources += [ "thread_task_runner.cc" ]
+  }
+
+  if (!is_nacl) {
+    sources += [ "unix_task_runner.cc" ]
   }
 
   if (enable_perfetto_stderr_crash_dump) {
     deps += [ ":debug_crash_stack_trace" ]
   }
+
+  if (enable_perfetto_version_gen) {
+    deps += [ ":version_gen_h" ]
+  }
+}
+
+if (enable_perfetto_version_gen) {
+  config("version_gen_config") {
+    include_dirs = [ root_gen_dir ]
+  }
+
+  # Note: the build file translators (tools/gn_utils.py) depend on the hardcoded
+  # "//src/base:version_gen_h". If you rename this target, update build file
+  # translators accordingly.
+  gen_perfetto_version_header("version_gen_h") {
+    cpp_out = "${root_gen_dir}/perfetto_version.gen.h"
+  }
 }
 
 if (enable_perfetto_stderr_crash_dump) {
@@ -73,7 +96,7 @@
 
 if (enable_perfetto_ipc) {
   # This cannot be in :base as it does not build on WASM.
-  source_set("unix_socket") {
+  perfetto_component("unix_socket") {
     deps = [
       "../../gn:default_deps",
       "../../include/perfetto/ext/base",
@@ -126,7 +149,11 @@
     "string_view_unittest.cc",
     "string_writer_unittest.cc",
     "subprocess_unittest.cc",
+    "task_runner_unittest.cc",
+    "temp_file_unittest.cc",
+    "thread_checker_unittest.cc",
     "time_unittest.cc",
+    "utils_unittest.cc",
     "uuid_unittest.cc",
     "weak_ptr_unittest.cc",
   ]
@@ -135,19 +162,18 @@
   if (!is_win) {
     sources += [
       "metatrace_unittest.cc",
-      "task_runner_unittest.cc",
-      "temp_file_unittest.cc",
-      "thread_checker_unittest.cc",
       "thread_task_runner_unittest.cc",
-      "utils_unittest.cc",
+      "watchdog_posix_unittest.cc",
     ]
   }
   if (perfetto_build_standalone || perfetto_build_with_android) {
     # This causes some problems on the chromium waterfall.
-    sources += [ "unix_socket_unittest.cc" ]
     if (is_linux || is_android) {
       sources += [ "watchdog_unittest.cc" ]
     }
+    if (!is_win) {
+      sources += [ "unix_socket_unittest.cc" ]
+    }
   }
 }
 
diff --git a/src/base/debug_crash_stack_trace.cc b/src/base/debug_crash_stack_trace.cc
index ae857a4..4da0fba 100644
--- a/src/base/debug_crash_stack_trace.cc
+++ b/src/base/debug_crash_stack_trace.cc
@@ -220,7 +220,7 @@
 // In order to retrigger it, we have to queue a new signal by calling
 // kill() ourselves.  The special case (si_pid == 0 && sig == SIGABRT) is
 // due to the kernel sending a SIGABRT from a user request via SysRQ.
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
     if (kill(getpid(), sig_num) < 0) {
 #else
     if (syscall(__NR_tgkill, getpid(), syscall(__NR_gettid), sig_num) < 0) {
diff --git a/src/base/event_fd.cc b/src/base/event_fd.cc
index 976db19..0856818 100644
--- a/src/base/event_fd.cc
+++ b/src/base/event_fd.cc
@@ -15,67 +15,96 @@
  */
 
 #include "perfetto/base/build_config.h"
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
+#include <errno.h>
 #include <stdint.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <synchapi.h>
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/eventfd.h>
 #include <unistd.h>
+#else  // Mac, Fuchsia and other non-Linux UNIXes
+#include <unistd.h>
+#endif
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/event_fd.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/utils.h"
 
-#if PERFETTO_USE_EVENTFD()
-#include <sys/eventfd.h>
-#endif
-
 namespace perfetto {
 namespace base {
 
-EventFd::EventFd() {
-#if PERFETTO_USE_EVENTFD()
-  fd_.reset(eventfd(/* start value */ 0, EFD_CLOEXEC | EFD_NONBLOCK));
-  PERFETTO_CHECK(fd_);
-#else
-  // Make the pipe non-blocking so that we never block the waking thread (either
-  // the main thread or another one) when scheduling a wake-up.
-  Pipe pipe = Pipe::Create(Pipe::kBothNonBlock);
-  fd_ = std::move(pipe.rd);
-  write_fd_ = std::move(pipe.wr);
-#endif  // !PERFETTO_USE_EVENTFD()
-}
-
 EventFd::~EventFd() = default;
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+EventFd::EventFd() {
+  event_handle_.reset(
+      CreateEventA(/*lpEventAttributes=*/nullptr, /*bManualReset=*/true,
+                   /*bInitialState=*/false, /*bInitialState=*/nullptr));
+}
+
 void EventFd::Notify() {
-  const uint64_t value = 1;
-
-#if PERFETTO_USE_EVENTFD()
-  ssize_t ret = write(fd_.get(), &value, sizeof(value));
-#else
-  ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t));
-#endif
-
-  if (ret <= 0 && errno != EAGAIN) {
-    PERFETTO_DFATAL("write()");
-  }
+  if (!SetEvent(event_handle_.get()))  // 0: fail, !0: success, unlike UNIX.
+    PERFETTO_DFATAL("EventFd::Notify()");
 }
 
 void EventFd::Clear() {
-#if PERFETTO_USE_EVENTFD()
+  if (!ResetEvent(event_handle_.get()))  // 0: fail, !0: success, unlike UNIX.
+    PERFETTO_DFATAL("EventFd::Clear()");
+}
+
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+
+EventFd::EventFd() {
+  event_handle_.reset(eventfd(/*initval=*/0, EFD_CLOEXEC | EFD_NONBLOCK));
+  PERFETTO_CHECK(event_handle_);
+}
+
+void EventFd::Notify() {
+  const uint64_t value = 1;
+  ssize_t ret = write(event_handle_.get(), &value, sizeof(value));
+  if (ret <= 0 && errno != EAGAIN)
+    PERFETTO_DFATAL("EventFd::Notify()");
+}
+
+void EventFd::Clear() {
   uint64_t value;
-  ssize_t ret = read(fd_.get(), &value, sizeof(value));
+  ssize_t ret = read(event_handle_.get(), &value, sizeof(value));
+  if (ret <= 0 && errno != EAGAIN)
+    PERFETTO_DFATAL("EventFd::Clear()");
+}
+
 #else
+
+EventFd::EventFd() {
+  // Make the pipe non-blocking so that we never block the waking thread (either
+  // the main thread or another one) when scheduling a wake-up.
+  Pipe pipe = Pipe::Create(Pipe::kBothNonBlock);
+  event_handle_ = ScopedPlatformHandle(std::move(pipe.rd).release());
+  write_fd_ = std::move(pipe.wr);
+}
+
+void EventFd::Notify() {
+  const uint64_t value = 1;
+  ssize_t ret = write(write_fd_.get(), &value, sizeof(uint8_t));
+  if (ret <= 0 && errno != EAGAIN)
+    PERFETTO_DFATAL("EventFd::Notify()");
+}
+
+void EventFd::Clear() {
   // Drain the byte(s) written to the wake-up pipe. We can potentially read
   // more than one byte if several wake-ups have been scheduled.
   char buffer[16];
-  ssize_t ret = read(fd_.get(), &buffer[0], sizeof(buffer));
-#endif
+  ssize_t ret = read(event_handle_.get(), &buffer[0], sizeof(buffer));
   if (ret <= 0 && errno != EAGAIN)
-    PERFETTO_DPLOG("read()");
+    PERFETTO_DFATAL("EventFd::Clear()");
 }
+#endif
 
 }  // namespace base
 }  // namespace perfetto
-
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index b8020ad..9b92af2 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -14,20 +14,26 @@
  * limitations under the License.
  */
 
+#include "perfetto/ext/base/file_utils.h"
+
 #include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/base/platform_handle.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) || \
-    PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
-#include <unistd.h>
-#else
-#include <corecrt_io.h>
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <direct.h>
 #include <io.h>
+#else
+#include <dirent.h>
+#include <unistd.h>
 #endif
 
 namespace perfetto {
@@ -36,6 +42,14 @@
 constexpr size_t kBufSize = 2048;
 }
 
+ssize_t Read(int fd, void* dst, size_t dst_size) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  return _read(fd, dst, static_cast<unsigned>(dst_size));
+#else
+  return PERFETTO_EINTR(read(fd, dst, dst_size));
+#endif
+}
+
 bool ReadFileDescriptor(int fd, std::string* out) {
   // Do not override existing data in string.
   size_t i = out->size();
@@ -51,7 +65,7 @@
     if (out->size() < i + kBufSize)
       out->resize(out->size() + kBufSize);
 
-    bytes_read = PERFETTO_EINTR(read(fd, &((*out)[i]), kBufSize));
+    bytes_read = Read(fd, &((*out)[i]), kBufSize);
     if (bytes_read > 0) {
       i += static_cast<size_t>(bytes_read);
     } else {
@@ -61,6 +75,35 @@
   }
 }
 
+bool ReadPlatformHandle(PlatformHandle h, std::string* out) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // Do not override existing data in string.
+  size_t i = out->size();
+
+  for (;;) {
+    if (out->size() < i + kBufSize)
+      out->resize(out->size() + kBufSize);
+    DWORD bytes_read = 0;
+    auto res = ::ReadFile(h, &((*out)[i]), kBufSize, &bytes_read, nullptr);
+    if (res && bytes_read > 0) {
+      i += static_cast<size_t>(bytes_read);
+    } else {
+      out->resize(i);
+      const bool is_eof = res && bytes_read == 0;
+      auto err = res ? 0 : GetLastError();
+      // The "Broken pipe" error on Windows is slighly different than Unix:
+      // On Unix: a "broken pipe" error can happen only on the writer side. On
+      // the reader there is no broken pipe, just a EOF.
+      // On windows: the reader also sees a broken pipe error.
+      // Here we normalize on the Unix behavior, treating broken pipe as EOF.
+      return is_eof || err == ERROR_BROKEN_PIPE;
+    }
+  }
+#else
+  return ReadFileDescriptor(h, out);
+#endif
+}
+
 bool ReadFileStream(FILE* f, std::string* out) {
   return ReadFileDescriptor(fileno(f), out);
 }
@@ -76,8 +119,11 @@
 ssize_t WriteAll(int fd, const void* buf, size_t count) {
   size_t written = 0;
   while (written < count) {
+    // write() on windows takes an unsigned int size.
+    uint32_t bytes_left = static_cast<uint32_t>(
+        std::min(count - written, static_cast<size_t>(UINT32_MAX)));
     ssize_t wr = PERFETTO_EINTR(
-        write(fd, static_cast<const char*>(buf) + written, count - written));
+        write(fd, static_cast<const char*>(buf) + written, bytes_left));
     if (wr == 0)
       break;
     if (wr < 0)
@@ -87,6 +133,19 @@
   return static_cast<ssize_t>(written);
 }
 
+ssize_t WriteAllHandle(PlatformHandle h, const void* buf, size_t count) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  DWORD wsize = 0;
+  if (::WriteFile(h, buf, static_cast<DWORD>(count), &wsize, nullptr)) {
+    return wsize;
+  } else {
+    return -1;
+  }
+#else
+  return WriteAll(h, buf, count);
+#endif
+}
+
 bool FlushFile(int fd) {
   PERFETTO_DCHECK(fd != 0);
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
@@ -99,5 +158,55 @@
 #endif
 }
 
+bool Mkdir(const std::string& path) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  return _mkdir(path.c_str()) == 0;
+#else
+  return mkdir(path.c_str(), 0755) == 0;
+#endif
+}
+
+bool Rmdir(const std::string& path) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  return _rmdir(path.c_str()) == 0;
+#else
+  return rmdir(path.c_str()) == 0;
+#endif
+}
+
+int CloseFile(int fd) {
+  return close(fd);
+}
+
+ScopedFile OpenFile(const std::string& path, int flags, FileOpenMode mode) {
+  PERFETTO_DCHECK((flags & O_CREAT) == 0 || mode != kFileModeInvalid);
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // Always use O_BINARY on Windows, to avoid silly EOL translations.
+  ScopedFile fd(_open(path.c_str(), flags | O_BINARY, mode));
+#else
+  // Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec.
+  ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode));
+#endif
+  return fd;
+}
+
+bool FileExists(const std::string& path) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  return _access(path.c_str(), 0) == 0;
+#else
+  return access(path.c_str(), F_OK) == 0;
+#endif
+}
+
+// Declared in base/platform_handle.h.
+int ClosePlatformHandle(PlatformHandle handle) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // Make the return value UNIX-style.
+  return CloseHandle(handle) ? 0 : -1;
+#else
+  return close(handle);
+#endif
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/metatrace.cc b/src/base/metatrace.cc
index 67d167c..c676d80 100644
--- a/src/base/metatrace.cc
+++ b/src/base/metatrace.cc
@@ -20,6 +20,7 @@
 #include "perfetto/base/task_runner.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/thread_annotations.h"
 
 namespace perfetto {
 namespace metatrace {
diff --git a/src/base/metatrace_unittest.cc b/src/base/metatrace_unittest.cc
index 88f1a67..d0c9bb0 100644
--- a/src/base/metatrace_unittest.cc
+++ b/src/base/metatrace_unittest.cc
@@ -15,11 +15,13 @@
  */
 
 #include <array>
+#include <atomic>
 #include <chrono>
 #include <deque>
 #include <thread>
 
 #include "perfetto/ext/base/metatrace.h"
+#include "perfetto/ext/base/thread_annotations.h"
 #include "src/base/test/test_task_runner.h"
 #include "test/gtest_and_gmock.h"
 
@@ -164,7 +166,7 @@
 // consistently without gaps.
 TEST_F(MetatraceTest, InterleavedReadWrites) {
   Enable(m::TAG_ANY);
-  constexpr int kMaxValue = m::RingBuffer::kCapacity * 10;
+  constexpr int kMaxValue = m::RingBuffer::kCapacity * 3;
 
   std::atomic<int> last_value_read{-1};
   auto read_task = [&last_value_read] {
@@ -172,8 +174,14 @@
     for (auto it = m::RingBuffer::GetReadIterator(); it; ++it) {
       if (it->type_and_id.load(std::memory_order_acquire) == 0)
         break;
-      EXPECT_EQ(it->counter_value, last + 1);
-      last = it->counter_value;
+      // TSan doesn't know about the happens-before relationship between the
+      // type_and_id marker and the value being valid. Fixing this properly
+      // would require making all accesses to the metatrace object as
+      // std::atomic and read them with memory_order_relaxed, which is overkill.
+      PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(&it->counter_value, sizeof(int), "")
+      int32_t counter_value = it->counter_value;
+      EXPECT_EQ(counter_value, last + 1);
+      last = counter_value;
     }
     // The read pointer is incremented only after destroying the iterator.
     // Publish the last read value after the loop.
@@ -190,8 +198,13 @@
       const int kCapacity = static_cast<int>(m::RingBuffer::kCapacity);
 
       // Wait for the reader to avoid overruns.
-      while (i - last_value_read >= kCapacity - 1)
-        std::this_thread::sleep_for(std::chrono::nanoseconds(1));
+      // Using memory_order_relaxed because the QEMU arm emulator seems to incur
+      // in very high costs when dealing with full barriers, causing timeouts.
+      for (int sleep_us = 1;
+           i - last_value_read.load(std::memory_order_relaxed) >= kCapacity - 1;
+           sleep_us = std::min(sleep_us * 10, 1000)) {
+        std::this_thread::sleep_for(std::chrono::microseconds(sleep_us));
+      }
     }
     task_runner_.PostTask(writer_done);
   });
diff --git a/src/base/optional_unittest.cc b/src/base/optional_unittest.cc
index 9c073ec..285db51 100644
--- a/src/base/optional_unittest.cc
+++ b/src/base/optional_unittest.cc
@@ -16,8 +16,10 @@
 
 // Comparisions of floats is used extensively in this file. Ignore warnings
 // as we want to stay close to Chromium.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
 
 #include <memory>
 #include <set>
@@ -2204,4 +2206,6 @@
 }  // namespace base
 }  // namespace perfetto
 
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC diagnostic pop
+#endif
diff --git a/src/base/paged_memory.cc b/src/base/paged_memory.cc
index c7feb53..551c5ca 100644
--- a/src/base/paged_memory.cc
+++ b/src/base/paged_memory.cc
@@ -34,39 +34,47 @@
 
 namespace {
 
-constexpr size_t kGuardSize = kPageSize;
-
 #if TRACK_COMMITTED_SIZE()
-constexpr size_t kCommitChunkSize = kPageSize * 1024;  // 4mB
-#endif                                                 // TRACK_COMMITTED_SIZE()
+constexpr size_t kCommitChunkSize = 4 * 1024 * 1024;  // 4MB
+#endif
+
+size_t RoundUpToSysPageSize(size_t req_size) {
+  const size_t page_size = GetSysPageSize();
+  return (req_size + page_size - 1) & ~(page_size - 1);
+}
+
+size_t GuardSize() {
+  return GetSysPageSize();
+}
 
 }  // namespace
 
 // static
-PagedMemory PagedMemory::Allocate(size_t size, int flags) {
-  PERFETTO_DCHECK(size % kPageSize == 0);
-  size_t outer_size = size + kGuardSize * 2;
+PagedMemory PagedMemory::Allocate(size_t req_size, int flags) {
+  size_t rounded_up_size = RoundUpToSysPageSize(req_size);
+  PERFETTO_CHECK(rounded_up_size >= req_size);
+  size_t outer_size = rounded_up_size + GuardSize() * 2;
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   void* ptr = VirtualAlloc(nullptr, outer_size, MEM_RESERVE, PAGE_NOACCESS);
   if (!ptr && (flags & kMayFail))
     return PagedMemory();
   PERFETTO_CHECK(ptr);
-  char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize;
+  char* usable_region = reinterpret_cast<char*>(ptr) + GuardSize();
 #else   // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   void* ptr = mmap(nullptr, outer_size, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
   if (ptr == MAP_FAILED && (flags & kMayFail))
     return PagedMemory();
   PERFETTO_CHECK(ptr && ptr != MAP_FAILED);
-  char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize;
-  int res = mprotect(ptr, kGuardSize, PROT_NONE);
-  res |= mprotect(usable_region + size, kGuardSize, PROT_NONE);
+  char* usable_region = reinterpret_cast<char*>(ptr) + GuardSize();
+  int res = mprotect(ptr, GuardSize(), PROT_NONE);
+  res |= mprotect(usable_region + rounded_up_size, GuardSize(), PROT_NONE);
   PERFETTO_CHECK(res == 0);
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
-  auto memory = PagedMemory(usable_region, size);
+  auto memory = PagedMemory(usable_region, req_size);
 #if TRACK_COMMITTED_SIZE()
-  size_t initial_commit = size;
+  size_t initial_commit = req_size;
   if (flags & kDontCommit)
     initial_commit = std::min(initial_commit, kCommitChunkSize);
   memory.EnsureCommitted(initial_commit);
@@ -97,12 +105,12 @@
   if (!p_)
     return;
   PERFETTO_CHECK(size_);
-  char* start = p_ - kGuardSize;
+  char* start = p_ - GuardSize();
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   BOOL res = VirtualFree(start, 0, MEM_RELEASE);
   PERFETTO_CHECK(res != 0);
 #else   // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  const size_t outer_size = size_ + kGuardSize * 2;
+  const size_t outer_size = RoundUpToSysPageSize(size_) + GuardSize() * 2;
   int res = munmap(start, outer_size);
   PERFETTO_CHECK(res == 0);
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/paged_memory_unittest.cc b/src/base/paged_memory_unittest.cc
index 06695d6..fd2790d 100644
--- a/src/base/paged_memory_unittest.cc
+++ b/src/base/paged_memory_unittest.cc
@@ -19,11 +19,12 @@
 #include <stdint.h>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/utils.h"
 #include "src/base/test/vm_test_utils.h"
 #include "test/gtest_and_gmock.h"
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
-    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&    \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&   \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 #include <sys/resource.h>
 #endif
@@ -63,6 +64,32 @@
 #endif
 }
 
+TEST(PagedMemoryTest, SubPageGranularity) {
+  const size_t kSize = GetSysPageSize() + 1024;
+  PagedMemory mem = PagedMemory::Allocate(kSize);
+  ASSERT_TRUE(mem.IsValid());
+  ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(mem.Get()) % GetSysPageSize());
+  void* ptr_raw = mem.Get();
+  for (size_t i = 0; i < kSize / sizeof(uint64_t); i++) {
+    auto* ptr64 = reinterpret_cast<volatile uint64_t*>(ptr_raw) + i;
+    ASSERT_EQ(0u, *ptr64);
+    *ptr64 = i;
+  }
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  // Do an AdviseDontNeed on the whole range, which is NOT an integer multiple
+  // of the page size. The initial page must be cleared. The remaining 1024
+  // might or might not be cleared depending on the OS implementation.
+  ASSERT_TRUE(mem.AdviseDontNeed(ptr_raw, kSize));
+  ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, GetSysPageSize()));
+  for (size_t i = 0; i < GetSysPageSize() / sizeof(uint64_t); i++) {
+    auto* ptr64 = reinterpret_cast<volatile uint64_t*>(ptr_raw) + i;
+    ASSERT_EQ(0u, *ptr64);
+  }
+#endif
+}
+
 TEST(PagedMemoryTest, Uncommitted) {
   constexpr size_t kNumPages = 4096;
   constexpr size_t kSize = 4096 * kNumPages;
@@ -137,7 +164,7 @@
 #endif  // ADDRESS_SANITIZER
 
 TEST(PagedMemoryTest, GuardRegions) {
-  const size_t kSize = 4096;
+  const size_t kSize = GetSysPageSize();
   PagedMemory mem = PagedMemory::Allocate(kSize);
   ASSERT_TRUE(mem.IsValid());
   volatile char* raw = reinterpret_cast<char*>(mem.Get());
@@ -149,7 +176,7 @@
 // MacOS: because it doesn't seem to have an equivalent rlimit to bound mmap().
 // Fuchsia: doesn't support rlimit.
 // Sanitizers: they seem to try to shadow mmaped memory and fail due to OOMs.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) &&                                 \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) &&                                  \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&                                    \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA) && !defined(ADDRESS_SANITIZER) && \
     !defined(LEAK_SANITIZER) && !defined(THREAD_SANITIZER) &&                  \
diff --git a/src/base/pipe.cc b/src/base/pipe.cc
index c61492f..f4912f7 100644
--- a/src/base/pipe.cc
+++ b/src/base/pipe.cc
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
-#include "perfetto/base/build_config.h"
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
 #include "perfetto/ext/base/pipe.h"
 
+#include "perfetto/base/build_config.h"
+
+#include <fcntl.h>  // For O_BINARY (Windows) and F_SETxx (UNIX)
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <namedpipeapi.h>
+#else
 #include <sys/types.h>
 #include <unistd.h>
+#endif
 
 #include "perfetto/base/logging.h"
 
@@ -32,15 +38,20 @@
 Pipe& Pipe::operator=(Pipe&&) = default;
 
 Pipe Pipe::Create(Flags flags) {
-  int fds[2];
+  PlatformHandle fds[2];
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  PERFETTO_CHECK(::CreatePipe(&fds[0], &fds[1], /*lpPipeAttributes=*/nullptr,
+                              0 /*default size*/));
+#else
   PERFETTO_CHECK(pipe(fds) == 0);
+  PERFETTO_CHECK(fcntl(fds[0], F_SETFD, FD_CLOEXEC) == 0);
+  PERFETTO_CHECK(fcntl(fds[1], F_SETFD, FD_CLOEXEC) == 0);
+#endif
   Pipe p;
   p.rd.reset(fds[0]);
   p.wr.reset(fds[1]);
 
-  PERFETTO_CHECK(fcntl(*p.rd, F_SETFD, FD_CLOEXEC) == 0);
-  PERFETTO_CHECK(fcntl(*p.wr, F_SETFD, FD_CLOEXEC) == 0);
-
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   if (flags == kBothNonBlock || flags == kRdNonBlock) {
     int cur_flags = fcntl(*p.rd, F_GETFL, 0);
     PERFETTO_CHECK(cur_flags >= 0);
@@ -52,10 +63,11 @@
     PERFETTO_CHECK(cur_flags >= 0);
     PERFETTO_CHECK(fcntl(*p.wr, F_SETFL, cur_flags | O_NONBLOCK) == 0);
   }
+#else
+  PERFETTO_CHECK(flags == kBothBlock);
+#endif
   return p;
 }
 
 }  // namespace base
 }  // namespace perfetto
-
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/scoped_file_unittest.cc b/src/base/scoped_file_unittest.cc
index dff4413..7590955 100644
--- a/src/base/scoped_file_unittest.cc
+++ b/src/base/scoped_file_unittest.cc
@@ -18,10 +18,11 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/base/build_config.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-#include <corecrt_io.h>
-#else
 #include <fcntl.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <io.h>
+#else
 #include <unistd.h>
 // Double closing of file handles on Windows leads to invocation of the invalid
 // parameter handler or asserts and therefore it cannot be tested, but it can
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/src/base/status.cc
similarity index 63%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to src/base/status.cc
index e22189e..30ccc47 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/src/base/status.cc
@@ -14,14 +14,22 @@
  * limitations under the License.
  */
 
-syntax = "proto2";
+#include "perfetto/base/status.h"
 
-package perfetto.protos;
+#include <stdarg.h>
 
-import "protos/perfetto/metrics/custom_options.proto";
+namespace perfetto {
+namespace base {
 
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+Status ErrStatus(const char* format, ...) {
+  char buffer[1024];
+  va_list ap;
+  va_start(ap, format);
+  vsnprintf(buffer, sizeof(buffer), format, ap);
+  va_end(ap);
+  Status status(buffer);
+  return status;
 }
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index dfd6716..7796aff 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -16,6 +16,7 @@
 
 #include "perfetto/ext/base/string_utils.h"
 
+#include <inttypes.h>
 #include <string.h>
 
 #include <algorithm>
@@ -25,6 +26,42 @@
 namespace perfetto {
 namespace base {
 
+std::string QuoteAndEscapeControlCodes(const std::string& raw) {
+  std::string ret;
+  for (auto it = raw.cbegin(); it != raw.cend(); it++) {
+    switch (*it) {
+      case '\\':
+        ret += "\\\\";
+        break;
+      case '"':
+        ret += "\\\"";
+        break;
+      case '/':
+        ret += "\\/";
+        break;
+      case '\b':
+        ret += "\\b";
+        break;
+      case '\f':
+        ret += "\\f";
+        break;
+      case '\n':
+        ret += "\\n";
+        break;
+      case '\r':
+        ret += "\\r";
+        break;
+      case '\t':
+        ret += "\\t";
+        break;
+      default:
+        ret += *it;
+        break;
+    }
+  }
+  return '"' + ret + '"';
+}
+
 bool StartsWith(const std::string& str, const std::string& prefix) {
   return str.compare(0, prefix.length(), prefix) == 0;
 }
@@ -40,7 +77,7 @@
 }
 
 size_t Find(const StringView& needle, const StringView& haystack) {
-  if (needle.size() == 0)
+  if (needle.empty())
     return 0;
   if (needle.size() > haystack.size())
     return std::string::npos;
@@ -138,6 +175,20 @@
   return buf;
 }
 
+std::string Uint64ToHexString(uint64_t number) {
+  return "0x" + Uint64ToHexStringNoPrefix(number);
+}
+
+std::string Uint64ToHexStringNoPrefix(uint64_t number) {
+  size_t max_size = 17;  // Max uint64 is FFFFFFFFFFFFFFFF + 1 for null byte.
+  std::string buf;
+  buf.resize(max_size);
+  auto final_size = snprintf(&buf[0], max_size, "%" PRIx64 "", number);
+  PERFETTO_DCHECK(final_size >= 0);
+  buf.resize(static_cast<size_t>(final_size));  // Cuts off the final null byte.
+  return buf;
+}
+
 std::string StripChars(const std::string& str,
                        const std::string& chars,
                        char replacement) {
@@ -161,5 +212,10 @@
   return str;
 }
 
+std::string TrimLeading(const std::string& str) {
+  size_t idx = str.find_first_not_of(' ');
+  return idx == std::string::npos ? str : str.substr(idx);
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index 6fba1fd..9fb341d 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -167,13 +167,28 @@
   EXPECT_EQ(ToHex("abc123"), "616263313233");
 }
 
-TEST(StringUtilsTest, intToHex) {
+TEST(StringUtilsTest, IntToHex) {
   EXPECT_EQ(IntToHexString(0), "0x00");
   EXPECT_EQ(IntToHexString(1), "0x01");
   EXPECT_EQ(IntToHexString(16), "0x10");
   EXPECT_EQ(IntToHexString(4294967295), "0xffffffff");
 }
 
+TEST(StringUtilsTest, Uint64ToHex) {
+  EXPECT_EQ(Uint64ToHexString(0), "0x0");
+  EXPECT_EQ(Uint64ToHexString(1), "0x1");
+  EXPECT_EQ(Uint64ToHexString(16), "0x10");
+  EXPECT_EQ(Uint64ToHexString(18446744073709551615UL), "0xffffffffffffffff");
+}
+
+TEST(StringUtilsTest, Uint64ToHexNoPrefix) {
+  EXPECT_EQ(Uint64ToHexStringNoPrefix(0), "0");
+  EXPECT_EQ(Uint64ToHexStringNoPrefix(1), "1");
+  EXPECT_EQ(Uint64ToHexStringNoPrefix(16), "10");
+  EXPECT_EQ(Uint64ToHexStringNoPrefix(18446744073709551615UL),
+            "ffffffffffffffff");
+}
+
 TEST(StringUtilsTest, CaseInsensitiveEqual) {
   EXPECT_TRUE(CaseInsensitiveEqual("", ""));
   EXPECT_TRUE(CaseInsensitiveEqual("abc", "abc"));
@@ -261,6 +276,13 @@
   EXPECT_EQ(ReplaceAll("abc", "c", "bbb"), "abbbb");
 }
 
+TEST(StringUtilsTest, TrimLeading) {
+  EXPECT_EQ(TrimLeading(""), "");
+  EXPECT_EQ(TrimLeading("a"), "a");
+  EXPECT_EQ(TrimLeading(" aaaa"), "aaaa");
+  EXPECT_EQ(TrimLeading(" aaaaa     "), "aaaaa     ");
+}
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/subprocess.cc b/src/base/subprocess_posix.cc
similarity index 71%
rename from src/base/subprocess.cc
rename to src/base/subprocess_posix.cc
index 6be24bd..ab9be44 100644
--- a/src/base/subprocess.cc
+++ b/src/base/subprocess_posix.cc
@@ -18,15 +18,18 @@
 
 #if PERFETTO_HAS_SUBPROCESS()
 
+#include <fcntl.h>
 #include <poll.h>
 #include <signal.h>
 #include <stdio.h>
+#include <sys/resource.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include <algorithm>
 #include <thread>
+#include <tuple>
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
@@ -104,6 +107,10 @@
       if (dup2(args->stdouterr_pipe_wr, STDOUT_FILENO) == -1)
         die("Failed to dup2(STDOUT)");
       break;
+    case Subprocess::kFd:
+      if (dup2(*args->create_args->out_fd, STDOUT_FILENO) == -1)
+        die("Failed to dup2(STDOUT)");
+      break;
   }
 
   switch (args->create_args->stderr_mode) {
@@ -118,6 +125,10 @@
       if (dup2(args->stdouterr_pipe_wr, STDERR_FILENO) == -1)
         die("Failed to dup2(STDERR)");
       break;
+    case Subprocess::kFd:
+      if (dup2(*args->create_args->out_fd, STDERR_FILENO) == -1)
+        die("Failed to dup2(STDERR)");
+      break;
   }
 
   // Close all FDs % stdin/out/err and the ones that the client explicitly
@@ -172,16 +183,32 @@
 Subprocess::Args::Args(Args&&) noexcept = default;
 Subprocess::Args& Subprocess::Args::operator=(Args&&) = default;
 
-Subprocess::Subprocess(std::initializer_list<std::string> _args)
-    : args(_args) {}
+Subprocess::Subprocess(std::initializer_list<std::string> a) : args(a) {
+  s_.rusage.reset(new ResourceUsage());
+}
 
-Subprocess::Subprocess(Subprocess&&) noexcept = default;
-Subprocess& Subprocess::operator=(Subprocess&&) = default;
+Subprocess::Subprocess(Subprocess&& other) noexcept {
+  static_assert(sizeof(Subprocess) == sizeof(std::tuple<MovableState, Args>),
+                "base::Subprocess' move ctor needs updating");
+  s_ = std::move(other.s_);
+  args = std::move(other.args);
+
+  // Reset the state of the moved-from object.
+  other.s_.status = kNotStarted;  // So the dtor doesn't try to kill().
+  other.~Subprocess();
+  new (&other) Subprocess();
+}
+
+Subprocess& Subprocess::operator=(Subprocess&& other) {
+  this->~Subprocess();
+  new (this) Subprocess(std::move(other));
+  return *this;
+}
 
 Subprocess::~Subprocess() {
-  if (status_ == kRunning)
+  if (s_.status == kRunning)
     KillAndWaitForTermination();
-  PERFETTO_CHECK(!waitpid_thread_.joinable());
+  PERFETTO_CHECK(!s_.waitpid_thread.joinable());
 }
 
 void Subprocess::Start() {
@@ -207,45 +234,60 @@
   }
 
   // Setup the pipes for stdin/err redirection.
-  stdin_pipe_ = base::Pipe::Create(base::Pipe::kWrNonBlock);
-  proc_args.stdin_pipe_rd = *stdin_pipe_.rd;
-  stdouterr_pipe_ = base::Pipe::Create(base::Pipe::kRdNonBlock);
-  proc_args.stdouterr_pipe_wr = *stdouterr_pipe_.wr;
+  s_.stdin_pipe = base::Pipe::Create(base::Pipe::kWrNonBlock);
+  proc_args.stdin_pipe_rd = *s_.stdin_pipe.rd;
+  s_.stdouterr_pipe = base::Pipe::Create(base::Pipe::kRdNonBlock);
+  proc_args.stdouterr_pipe_wr = *s_.stdouterr_pipe.wr;
 
   // Spawn the child process that will exec().
-  pid_ = fork();
-  PERFETTO_CHECK(pid_ >= 0);
-  if (pid_ == 0) {
+  s_.pid = fork();
+  PERFETTO_CHECK(s_.pid >= 0);
+  if (s_.pid == 0) {
     // Close the parent-ends of the pipes.
-    stdin_pipe_.wr.reset();
-    stdouterr_pipe_.rd.reset();
+    s_.stdin_pipe.wr.reset();
+    s_.stdouterr_pipe.rd.reset();
     ChildProcess(&proc_args);
     // ChildProcess() doesn't return, not even in case of failures.
     PERFETTO_FATAL("not reached");
   }
 
-  status_ = kRunning;
+  s_.status = kRunning;
 
   // Close the child-end of the pipes.
-  // Deliberately NOT closing the stdin_pipe_.rd. This is to avoid crashing
+  // Deliberately NOT closing the s_.stdin_pipe.rd. This is to avoid crashing
   // with a SIGPIPE if the process exits without consuming its stdin, while
   // the parent tries to write() on the other end of the stdin pipe.
-  stdouterr_pipe_.wr.reset();
+  s_.stdouterr_pipe.wr.reset();
+  proc_args.create_args->out_fd.reset();
 
   // Spawn a thread that is blocked on waitpid() and writes the termination
   // status onto a pipe. The problem here is that waipid() doesn't have a
   // timeout option and can't be passed to poll(). The alternative would be
   // using a SIGCHLD handler, but anecdotally signal handlers introduce more
   // problems than what they solve.
-  exit_status_pipe_ = base::Pipe::Create(base::Pipe::kRdNonBlock);
+  s_.exit_status_pipe = base::Pipe::Create(base::Pipe::kRdNonBlock);
 
   // Both ends of the pipe are closed after the thread.join().
-  int pid = pid_;
-  int exit_status_pipe_wr = exit_status_pipe_.wr.release();
-  waitpid_thread_ = std::thread([pid, exit_status_pipe_wr] {
+  int pid = s_.pid;
+  int exit_status_pipe_wr = s_.exit_status_pipe.wr.release();
+  auto* rusage = s_.rusage.get();
+  s_.waitpid_thread = std::thread([pid, exit_status_pipe_wr, rusage] {
     int pid_stat = -1;
-    int wait_res = PERFETTO_EINTR(waitpid(pid, &pid_stat, 0));
+    struct rusage usg {};
+    int wait_res = PERFETTO_EINTR(wait4(pid, &pid_stat, 0, &usg));
     PERFETTO_CHECK(wait_res == pid);
+
+    auto tv_to_ms = [](const struct timeval& tv) {
+      return static_cast<uint32_t>(tv.tv_sec * 1000 + tv.tv_usec / 1000);
+    };
+    rusage->cpu_utime_ms = tv_to_ms(usg.ru_utime);
+    rusage->cpu_stime_ms = tv_to_ms(usg.ru_stime);
+    rusage->max_rss_kb = static_cast<uint32_t>(usg.ru_maxrss) / 1000;
+    rusage->min_page_faults = static_cast<uint32_t>(usg.ru_minflt);
+    rusage->maj_page_faults = static_cast<uint32_t>(usg.ru_majflt);
+    rusage->vol_ctx_switch = static_cast<uint32_t>(usg.ru_nvcsw);
+    rusage->invol_ctx_switch = static_cast<uint32_t>(usg.ru_nivcsw);
+
     base::ignore_result(PERFETTO_EINTR(
         write(exit_status_pipe_wr, &pid_stat, sizeof(pid_stat))));
     PERFETTO_CHECK(close(exit_status_pipe_wr) == 0 || errno == EINTR);
@@ -253,11 +295,11 @@
 }
 
 Subprocess::Status Subprocess::Poll() {
-  if (status_ != kRunning)
-    return status_;  // Nothing to poll.
+  if (s_.status != kRunning)
+    return s_.status;  // Nothing to poll.
   while (PollInternal(0 /* don't block*/)) {
   }
-  return status_;
+  return s_.status;
 }
 
 // |timeout_ms| semantic:
@@ -270,18 +312,18 @@
 bool Subprocess::PollInternal(int poll_timeout_ms) {
   struct pollfd fds[3]{};
   size_t num_fds = 0;
-  if (exit_status_pipe_.rd) {
-    fds[num_fds].fd = *exit_status_pipe_.rd;
+  if (s_.exit_status_pipe.rd) {
+    fds[num_fds].fd = *s_.exit_status_pipe.rd;
     fds[num_fds].events = POLLIN;
     num_fds++;
   }
-  if (stdouterr_pipe_.rd) {
-    fds[num_fds].fd = *stdouterr_pipe_.rd;
+  if (s_.stdouterr_pipe.rd) {
+    fds[num_fds].fd = *s_.stdouterr_pipe.rd;
     fds[num_fds].events = POLLIN;
     num_fds++;
   }
-  if (stdin_pipe_.wr) {
-    fds[num_fds].fd = *stdin_pipe_.wr;
+  if (s_.stdin_pipe.wr) {
+    fds[num_fds].fd = *s_.stdin_pipe.wr;
     fds[num_fds].events = POLLOUT;
     num_fds++;
   }
@@ -301,7 +343,7 @@
 }
 
 bool Subprocess::Wait(int timeout_ms) {
-  PERFETTO_CHECK(status_ != kNotStarted);
+  PERFETTO_CHECK(s_.status != kNotStarted);
 
   // Break out of the loop only after both conditions are satisfied:
   // - All stdout/stderr data has been read (if kBuffer).
@@ -315,7 +357,7 @@
   // state where the write(stdin_pipe_.wr) will never unblock.
 
   const int64_t t_start = base::GetWallTimeMs().count();
-  while (exit_status_pipe_.rd || stdouterr_pipe_.rd) {
+  while (s_.exit_status_pipe.rd || s_.stdouterr_pipe.rd) {
     int poll_timeout_ms = -1;  // Block until a FD is ready.
     if (timeout_ms > 0) {
       const int64_t now = GetWallTimeMs().count();
@@ -329,42 +371,42 @@
 }
 
 bool Subprocess::Call(int timeout_ms) {
-  PERFETTO_CHECK(status_ == kNotStarted);
+  PERFETTO_CHECK(s_.status == kNotStarted);
   Start();
 
   if (!Wait(timeout_ms)) {
     KillAndWaitForTermination();
     // TryReadExitStatus must have joined the thread.
-    PERFETTO_DCHECK(!waitpid_thread_.joinable());
+    PERFETTO_DCHECK(!s_.waitpid_thread.joinable());
   }
-  PERFETTO_DCHECK(status_ != kRunning);
-  return status_ == kExited && returncode_ == 0;
+  PERFETTO_DCHECK(s_.status != kRunning);
+  return s_.status == kExited && s_.returncode == 0;
 }
 
 void Subprocess::TryReadExitStatus() {
-  if (!exit_status_pipe_.rd)
+  if (!s_.exit_status_pipe.rd)
     return;
 
   int pid_stat = -1;
-  int64_t rsize =
-      PERFETTO_EINTR(read(*exit_status_pipe_.rd, &pid_stat, sizeof(pid_stat)));
+  int64_t rsize = PERFETTO_EINTR(
+      read(*s_.exit_status_pipe.rd, &pid_stat, sizeof(pid_stat)));
   if (rsize < 0 && errno == EAGAIN)
     return;
 
   if (rsize > 0) {
     PERFETTO_CHECK(rsize == sizeof(pid_stat));
   } else if (rsize < 0) {
-    PERFETTO_PLOG("Subprocess read(exit_status_pipe_) failed");
+    PERFETTO_PLOG("Subprocess read(s_.exit_status_pipe) failed");
   }
-  waitpid_thread_.join();
-  exit_status_pipe_.rd.reset();
+  s_.waitpid_thread.join();
+  s_.exit_status_pipe.rd.reset();
 
   if (WIFEXITED(pid_stat)) {
-    returncode_ = WEXITSTATUS(pid_stat);
-    status_ = kExited;
+    s_.returncode = WEXITSTATUS(pid_stat);
+    s_.status = kExited;
   } else if (WIFSIGNALED(pid_stat)) {
-    returncode_ = 128 + WTERMSIG(pid_stat);  // Follow bash convention.
-    status_ = kKilledBySignal;
+    s_.returncode = 128 + WTERMSIG(pid_stat);  // Follow bash convention.
+    s_.status = kKilledBySignal;
   } else {
     PERFETTO_FATAL("waitpid() returned an unexpected value (0x%x)", pid_stat);
   }
@@ -372,51 +414,51 @@
 
 // If the stidn pipe is still open, push input data and close it at the end.
 void Subprocess::TryPushStdin() {
-  if (!stdin_pipe_.wr)
+  if (!s_.stdin_pipe.wr)
     return;
 
-  PERFETTO_DCHECK(args.input.empty() || input_written_ < args.input.size());
+  PERFETTO_DCHECK(args.input.empty() || s_.input_written < args.input.size());
   if (args.input.size()) {
     int64_t wsize =
-        PERFETTO_EINTR(write(*stdin_pipe_.wr, &args.input[input_written_],
-                             args.input.size() - input_written_));
+        PERFETTO_EINTR(write(*s_.stdin_pipe.wr, &args.input[s_.input_written],
+                             args.input.size() - s_.input_written));
     if (wsize < 0 && errno == EAGAIN)
       return;
 
     if (wsize >= 0) {
       // Whether write() can return 0 is one of the greatest mysteries of UNIX.
       // Just ignore it.
-      input_written_ += static_cast<size_t>(wsize);
+      s_.input_written += static_cast<size_t>(wsize);
     } else {
       PERFETTO_PLOG("Subprocess write(stdin) failed");
-      stdin_pipe_.wr.reset();
+      s_.stdin_pipe.wr.reset();
     }
   }
-  PERFETTO_DCHECK(input_written_ <= args.input.size());
-  if (input_written_ == args.input.size())
-    stdin_pipe_.wr.reset();  // Close stdin.
+  PERFETTO_DCHECK(s_.input_written <= args.input.size());
+  if (s_.input_written == args.input.size())
+    s_.stdin_pipe.wr.reset();  // Close stdin.
 }
 
 void Subprocess::TryReadStdoutAndErr() {
-  if (!stdouterr_pipe_.rd)
+  if (!s_.stdouterr_pipe.rd)
     return;
   char buf[4096];
-  int64_t rsize = PERFETTO_EINTR(read(*stdouterr_pipe_.rd, buf, sizeof(buf)));
+  int64_t rsize = PERFETTO_EINTR(read(*s_.stdouterr_pipe.rd, buf, sizeof(buf)));
   if (rsize < 0 && errno == EAGAIN)
     return;
 
   if (rsize > 0) {
-    output_.append(buf, static_cast<size_t>(rsize));
+    s_.output.append(buf, static_cast<size_t>(rsize));
   } else if (rsize == 0 /* EOF */) {
-    stdouterr_pipe_.rd.reset();
+    s_.stdouterr_pipe.rd.reset();
   } else {
     PERFETTO_PLOG("Subprocess read(stdout/err) failed");
-    stdouterr_pipe_.rd.reset();
+    s_.stdouterr_pipe.rd.reset();
   }
 }
 
-void Subprocess::KillAndWaitForTermination() {
-  kill(pid_, SIGKILL);
+void Subprocess::KillAndWaitForTermination(int sig_num) {
+  kill(s_.pid, sig_num ? sig_num : SIGKILL);
   Wait();
 }
 
diff --git a/src/base/subprocess_unittest.cc b/src/base/subprocess_unittest.cc
index 6bcdc99..5f8c5d6 100644
--- a/src/base/subprocess_unittest.cc
+++ b/src/base/subprocess_unittest.cc
@@ -23,7 +23,9 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "test/gtest_and_gmock.h"
 
@@ -171,22 +173,26 @@
   EXPECT_EQ(p.returncode(), 128 + SIGKILL);
 }
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && defined(ADDRESS_SANITIZER)
-#define MAYBE_PollBehavesProperly DISABLED_PollBehavesProperly
-#else
-#define MAYBE_PollBehavesProperly PollBehavesProperly
-#endif
-
-// TODO(b/158484911): Re-enable once problem is fixed.
-TEST(SubprocessTest, MAYBE_PollBehavesProperly) {
-  Subprocess p({"sh", "-c", "echo foobar"});
-  p.args.stdout_mode = Subprocess::kBuffer;
-  p.args.input = "ignored";
+TEST(SubprocessTest, PollBehavesProperly) {
+  Pipe pipe = Pipe::Create();
+  Subprocess p({"true"});
+  p.args.stdout_mode = Subprocess::kFd;
+  p.args.out_fd = std::move(pipe.wr);
   p.Start();
 
-  // Here we use kill() as a way to tell if the process is still running.
-  // SIGWINCH is ignored by default.
-  while (kill(p.pid(), SIGWINCH) == 0) {
+  // Wait for EOF (which really means the child process has terminated).
+  char buf;
+  while (PERFETTO_EINTR(read(*pipe.rd, &buf, 1)) != 0) {
+    usleep(1000);
+  }
+
+  // The kernel takes some time to detect the termination of the process. The
+  // best thing we can do here is check that we detect the termination within
+  // some reasonable time.
+  auto start_ms = GetWallTimeMs();
+  while (p.Poll() != Subprocess::kExited) {
+    auto elapsed_ms = GetWallTimeMs() - start_ms;
+    ASSERT_LT(elapsed_ms, TimeMillis(10000));
     usleep(1000);
   }
 
@@ -243,14 +249,18 @@
 }
 
 TEST(SubprocessTest, Wait) {
-  Subprocess p({"sleep", "10000"});
+  Subprocess p({"sh", "-c", "echo exec_done; while true; do true; done"});
+  p.args.stdout_mode = Subprocess::kBuffer;
   p.Start();
-  for (int i = 0; i < 3; i++) {
+
+  // Wait for the fork()+exec() to complete.
+  while (p.output().find("exec_done") == std::string::npos) {
     EXPECT_FALSE(p.Wait(1 /*ms*/));
     EXPECT_EQ(p.status(), Subprocess::kRunning);
   }
+
   kill(p.pid(), SIGBUS);
-  EXPECT_TRUE(p.Wait(30000 /*ms*/));
+  EXPECT_TRUE(p.Wait(30000 /*ms*/));  // We shouldn't hit this.
   EXPECT_TRUE(p.Wait());  // Should be a no-op.
   EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
   EXPECT_EQ(p.returncode(), 128 + SIGBUS);
@@ -269,6 +279,34 @@
   EXPECT_EQ(kill(pid, SIGWINCH), -1);
 }
 
+// Regression test for b/162505491.
+TEST(SubprocessTest, MoveOperators) {
+  {
+    Subprocess initial = Subprocess({"sleep", "10000"});
+    initial.Start();
+    Subprocess moved(std::move(initial));
+    EXPECT_EQ(moved.Poll(), Subprocess::kRunning);
+    EXPECT_EQ(initial.Poll(), Subprocess::kNotStarted);
+
+    // Check that reuse works
+    initial = Subprocess({"echo", "-n", "hello"});
+    initial.args.stdout_mode = Subprocess::OutputMode::kBuffer;
+    initial.Start();
+    initial.Wait(/*timeout=*/5000);
+    EXPECT_EQ(initial.status(), Subprocess::kExited);
+    EXPECT_EQ(initial.returncode(), 0);
+    EXPECT_EQ(initial.output(), "hello");
+  }
+
+  std::vector<Subprocess> v;
+  for (int i = 0; i < 10; i++) {
+    v.emplace_back(Subprocess({"sleep", "10"}));
+    v.back().Start();
+  }
+  for (auto& p : v)
+    EXPECT_EQ(p.Poll(), Subprocess::kRunning);
+}
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/task_runner_unittest.cc b/src/base/task_runner_unittest.cc
index 10f047b..ae2297a 100644
--- a/src/base/task_runner_unittest.cc
+++ b/src/base/task_runner_unittest.cc
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
+#include "perfetto/base/build_config.h"
+
 #include "perfetto/ext/base/unix_task_runner.h"
 
 #include <thread>
 
-#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/event_fd.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/scoped_file.h"
@@ -35,25 +37,6 @@
   UnixTaskRunner task_runner;
 };
 
-struct TestPipe : Pipe {
-  TestPipe() : Pipe(Pipe::Create()) {
-    // Make the pipe initially readable.
-    Write();
-  }
-
-  void Read() {
-    char b;
-    ssize_t rd = read(*this->rd, &b, 1);
-    PERFETTO_DCHECK(rd == 1);
-  }
-
-  void Write() {
-    const char b = '?';
-    ssize_t wr = WriteAll(*this->wr, &b, 1);
-    PERFETTO_DCHECK(wr == 1);
-  }
-};
-
 TEST_F(TaskRunnerTest, PostImmediateTask) {
   auto& task_runner = this->task_runner;
   int counter = 0;
@@ -124,20 +107,22 @@
 
 TEST_F(TaskRunnerTest, AddFileDescriptorWatch) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  EventFd evt;
+  task_runner.AddFileDescriptorWatch(evt.fd(),
                                      [&task_runner] { task_runner.Quit(); });
+  evt.Notify();
   task_runner.Run();
 }
 
 TEST_F(TaskRunnerTest, RemoveFileDescriptorWatch) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
 
   bool watch_ran = false;
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt.fd(),
                                      [&watch_ran] { watch_ran = true; });
-  task_runner.RemoveFileDescriptorWatch(pipe.rd.get());
+  task_runner.RemoveFileDescriptorWatch(evt.fd());
   task_runner.PostDelayedTask([&task_runner] { task_runner.Quit(); }, 10);
   task_runner.Run();
 
@@ -146,13 +131,14 @@
 
 TEST_F(TaskRunnerTest, RemoveFileDescriptorWatchFromTask) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
 
   bool watch_ran = false;
-  task_runner.PostTask([&task_runner, &pipe] {
-    task_runner.RemoveFileDescriptorWatch(pipe.rd.get());
+  task_runner.PostTask([&task_runner, &evt] {
+    task_runner.RemoveFileDescriptorWatch(evt.fd());
   });
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt.fd(),
                                      [&watch_ran] { watch_ran = true; });
   task_runner.PostDelayedTask([&task_runner] { task_runner.Quit(); }, 10);
   task_runner.Run();
@@ -162,30 +148,31 @@
 
 TEST_F(TaskRunnerTest, AddFileDescriptorWatchFromAnotherWatch) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
-  TestPipe pipe2;
-
-  task_runner.AddFileDescriptorWatch(
-      pipe.rd.get(), [&task_runner, &pipe, &pipe2] {
-        pipe.Read();
-        task_runner.AddFileDescriptorWatch(
-            pipe2.rd.get(), [&task_runner] { task_runner.Quit(); });
-      });
+  EventFd evt;
+  EventFd evt2;
+  evt.Notify();
+  evt2.Notify();
+  task_runner.AddFileDescriptorWatch(evt.fd(), [&task_runner, &evt, &evt2] {
+    evt.Clear();
+    task_runner.AddFileDescriptorWatch(evt2.fd(),
+                                       [&task_runner] { task_runner.Quit(); });
+  });
   task_runner.Run();
 }
 
 TEST_F(TaskRunnerTest, RemoveFileDescriptorWatchFromAnotherWatch) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
-  TestPipe pipe2;
+  EventFd evt;
+  EventFd evt2;
+  evt.Notify();
 
   bool watch_ran = false;
-  task_runner.AddFileDescriptorWatch(
-      pipe.rd.get(), [&task_runner, &pipe, &pipe2] {
-        pipe.Read();
-        task_runner.RemoveFileDescriptorWatch(pipe2.rd.get());
-      });
-  task_runner.AddFileDescriptorWatch(pipe2.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt.fd(), [&task_runner, &evt, &evt2] {
+    evt.Clear();
+    evt2.Notify();
+    task_runner.RemoveFileDescriptorWatch(evt2.fd());
+  });
+  task_runner.AddFileDescriptorWatch(evt2.fd(),
                                      [&watch_ran] { watch_ran = true; });
   task_runner.PostDelayedTask([&task_runner] { task_runner.Quit(); }, 10);
   task_runner.Run();
@@ -195,16 +182,19 @@
 
 TEST_F(TaskRunnerTest, ReplaceFileDescriptorWatchFromAnotherWatch) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
-  TestPipe pipe2;
+  EventFd evt;
+  EventFd evt2;
 
   bool watch_ran = false;
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(), [&task_runner, &pipe2] {
-    task_runner.RemoveFileDescriptorWatch(pipe2.rd.get());
-    task_runner.AddFileDescriptorWatch(pipe2.rd.get(),
+  evt.Notify();
+  task_runner.AddFileDescriptorWatch(evt.fd(), [&task_runner, &evt, &evt2] {
+    evt.Clear();
+    evt2.Notify();
+    task_runner.RemoveFileDescriptorWatch(evt2.fd());
+    task_runner.AddFileDescriptorWatch(evt2.fd(),
                                        [&task_runner] { task_runner.Quit(); });
   });
-  task_runner.AddFileDescriptorWatch(pipe2.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt2.fd(),
                                      [&watch_ran] { watch_ran = true; });
   task_runner.Run();
 
@@ -213,10 +203,11 @@
 
 TEST_F(TaskRunnerTest, AddFileDescriptorWatchFromAnotherThread) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
 
-  std::thread thread([&task_runner, &pipe] {
-    task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  std::thread thread([&task_runner, &evt] {
+    task_runner.AddFileDescriptorWatch(evt.fd(),
                                        [&task_runner] { task_runner.Quit(); });
   });
   task_runner.Run();
@@ -225,30 +216,23 @@
 
 TEST_F(TaskRunnerTest, FileDescriptorWatchWithMultipleEvents) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
 
   int event_count = 0;
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
-                                     [&task_runner, &pipe, &event_count] {
-                                       if (++event_count == 3) {
-                                         task_runner.Quit();
-                                         return;
-                                       }
-                                       pipe.Read();
-                                     });
-  task_runner.PostTask([&pipe] { pipe.Write(); });
-  task_runner.PostTask([&pipe] { pipe.Write(); });
+  task_runner.AddFileDescriptorWatch(
+      evt.fd(), [&task_runner, &evt, &event_count] {
+        ASSERT_LT(event_count, 3);
+        if (++event_count == 3) {
+          task_runner.Quit();
+          return;
+        }
+        evt.Clear();
+        task_runner.PostTask([&evt] { evt.Notify(); });
+      });
   task_runner.Run();
 }
 
-TEST_F(TaskRunnerTest, FileDescriptorClosedEvent) {
-  auto& task_runner = this->task_runner;
-  TestPipe pipe;
-  pipe.wr.reset();
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
-                                     [&task_runner] { task_runner.Quit(); });
-  task_runner.Run();
-}
 
 TEST_F(TaskRunnerTest, PostManyDelayedTasks) {
   // Check that PostTask doesn't start failing if there are too many scheduled
@@ -282,9 +266,11 @@
 
 TEST_F(TaskRunnerTest, FileDescriptorWatchesNotStarved) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
+
   task_runner.PostTask(std::bind(&RepeatingTask, &task_runner));
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt.fd(),
                                      [&task_runner] { task_runner.Quit(); });
   task_runner.Run();
 }
@@ -294,17 +280,20 @@
     task_runner->Quit();
     return;
   }
-  task_runner->PostTask(std::bind(&CountdownTask, task_runner, counter));
+  task_runner->PostDelayedTask(std::bind(&CountdownTask, task_runner, counter),
+                               1);
 }
 
 TEST_F(TaskRunnerTest, NoDuplicateFileDescriptorWatchCallbacks) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
+
   bool watch_called = 0;
   int counter = 10;
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(), [&pipe, &watch_called] {
+  task_runner.AddFileDescriptorWatch(evt.fd(), [&evt, &watch_called] {
     ASSERT_FALSE(watch_called);
-    pipe.Read();
+    evt.Clear();
     watch_called = true;
   });
   task_runner.PostTask(std::bind(&CountdownTask, &task_runner, &counter));
@@ -313,16 +302,17 @@
 
 TEST_F(TaskRunnerTest, ReplaceFileDescriptorWatchFromOtherThread) {
   auto& task_runner = this->task_runner;
-  TestPipe pipe;
+  EventFd evt;
+  evt.Notify();
 
   // The two watch tasks here race each other. We don't particularly care which
   // wins as long as one of them runs.
-  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  task_runner.AddFileDescriptorWatch(evt.fd(),
                                      [&task_runner] { task_runner.Quit(); });
 
-  std::thread thread([&task_runner, &pipe] {
-    task_runner.RemoveFileDescriptorWatch(pipe.rd.get());
-    task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+  std::thread thread([&task_runner, &evt] {
+    task_runner.RemoveFileDescriptorWatch(evt.fd());
+    task_runner.AddFileDescriptorWatch(evt.fd(),
                                        [&task_runner] { task_runner.Quit(); });
   });
 
@@ -357,6 +347,46 @@
   thread.join();
 }
 
+TEST_F(TaskRunnerTest, FileDescriptorWatchFairness) {
+  auto& task_runner = this->task_runner;
+  EventFd evt[5];
+  std::map<PlatformHandle, int /*num_tasks*/> num_tasks;
+  static constexpr int kNumTasksPerHandle = 100;
+  for (auto& e : evt) {
+    e.Notify();
+    task_runner.AddFileDescriptorWatch(e.fd(), [&] {
+      if (++num_tasks[e.fd()] == kNumTasksPerHandle) {
+        e.Clear();
+        task_runner.Quit();
+      }
+    });
+  }
+
+  task_runner.Run();
+
+  // The sequence evt[0], evt[1], evt[2] should be repeated N times. On the
+  // Nth time the task runner quits. All tasks should have been running at least
+  // N-1 times (we can't predict which one of the tasks will quit).
+  for (auto& e : evt) {
+    ASSERT_GE(num_tasks[e.fd()], kNumTasksPerHandle - 1);
+    ASSERT_LE(num_tasks[e.fd()], kNumTasksPerHandle);
+  }
+}
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+// This tests UNIX-specific behavior on pipe closure.
+TEST_F(TaskRunnerTest, FileDescriptorClosedEvent) {
+  auto& task_runner = this->task_runner;
+  Pipe pipe = Pipe::Create();
+  pipe.wr.reset();
+  task_runner.AddFileDescriptorWatch(pipe.rd.get(),
+                                     [&task_runner] { task_runner.Quit(); });
+  task_runner.Run();
+}
+
+#endif
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/temp_file.cc b/src/base/temp_file.cc
index 59a5089..11e3f37 100644
--- a/src/base/temp_file.cc
+++ b/src/base/temp_file.cc
@@ -14,36 +14,81 @@
  * limitations under the License.
  */
 
-#include "perfetto/base/build_config.h"
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
 #include "perfetto/ext/base/temp_file.h"
 
+#include "perfetto/base/build_config.h"
+
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <direct.h>
+#include <fileapi.h>
+#include <io.h>
+#else
 #include <unistd.h>
+#endif
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/string_utils.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+namespace {
+std::string GetTempName() {
+  char name[] = "perfetto-XXXXXX";
+  PERFETTO_CHECK(_mktemp_s(name, sizeof(name)) == 0);
+  return name;
+}
+}  // namespace
+#endif
 
 namespace perfetto {
 namespace base {
 
-namespace {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-constexpr char kSysTmpPath[] = "/data/local/tmp";
+std::string GetSysTempDir() {
+  const char* tmpdir = nullptr;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  if ((tmpdir = getenv("TMP")))
+    return tmpdir;
+  if ((tmpdir = getenv("TEMP")))
+    return tmpdir;
+  return "C:\\TEMP";
 #else
-constexpr char kSysTmpPath[] = "/tmp";
-#endif
-}  // namespace
+  if ((tmpdir = getenv("TMPDIR")))
+    return base::StripSuffix(tmpdir, "/");
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  return "/data/local/tmp";
+#else
+  return "/tmp";
+#endif  // !OS_ANDROID
+#endif  // !OS_WIN
+}
 
 // static
 TempFile TempFile::Create() {
   TempFile temp_file;
-  const char* tmpdir = getenv("TMPDIR");
-  if (tmpdir) {
-    temp_file.path_.assign(tmpdir);
-  } else {
-    temp_file.path_.assign(kSysTmpPath);
-  }
-  temp_file.path_.append("/perfetto-XXXXXXXX");
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  temp_file.path_ = GetSysTempDir() + "\\" + GetTempName();
+  // Several tests want to read-back the temp file while still open. On Windows,
+  // that requires FILE_SHARE_READ. FILE_SHARE_READ is NOT settable when using
+  // the POSIX-compat equivalent function _open(). Hence the CreateFileA +
+  // _open_osfhandle dance here.
+  HANDLE h =
+      ::CreateFileA(temp_file.path_.c_str(), GENERIC_READ | GENERIC_WRITE,
+                    FILE_SHARE_DELETE | FILE_SHARE_READ, nullptr, CREATE_ALWAYS,
+                    FILE_ATTRIBUTE_TEMPORARY, nullptr);
+  PERFETTO_CHECK(PlatformHandleChecker::IsValid(h));
+  // According to MSDN, when using _open_osfhandle the caller must not call
+  // CloseHandle(). Ownership is moved to the file descriptor, which then needs
+  // to be closed with just with _close().
+  temp_file.fd_.reset(_open_osfhandle(reinterpret_cast<intptr_t>(h), 0));
+#else
+  temp_file.path_ = GetSysTempDir() + "/perfetto-XXXXXXXX";
   temp_file.fd_.reset(mkstemp(&temp_file.path_[0]));
+#endif
   if (PERFETTO_UNLIKELY(!temp_file.fd_)) {
     PERFETTO_FATAL("Could not create temp file %s", temp_file.path_.c_str());
   }
@@ -71,7 +116,13 @@
 void TempFile::Unlink() {
   if (path_.empty())
     return;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // If the FD is still open DeleteFile will mark the file as pending deletion
+  // and delete it only when the process exists.
+  PERFETTO_CHECK(DeleteFileA(path_.c_str()));
+#else
   PERFETTO_CHECK(unlink(path_.c_str()) == 0);
+#endif
   path_.clear();
 }
 
@@ -81,20 +132,25 @@
 // static
 TempDir TempDir::Create() {
   TempDir temp_dir;
-  temp_dir.path_.assign(kSysTmpPath);
-  temp_dir.path_.append("/perfetto-XXXXXXXX");
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  temp_dir.path_ = GetSysTempDir() + "\\" + GetTempName();
+  PERFETTO_CHECK(_mkdir(temp_dir.path_.c_str()) == 0);
+#else
+  temp_dir.path_ = GetSysTempDir() + "/perfetto-XXXXXXXX";
   PERFETTO_CHECK(mkdtemp(&temp_dir.path_[0]));
+#endif
   return temp_dir;
 }
 
 TempDir::TempDir() = default;
+TempDir::TempDir(TempDir&&) noexcept = default;
+TempDir& TempDir::operator=(TempDir&&) = default;
 
 TempDir::~TempDir() {
-  PERFETTO_CHECK(rmdir(path_.c_str()) == 0);
+  if (path_.empty())
+    return;  // For objects that get std::move()d.
+  PERFETTO_CHECK(Rmdir(path_));
 }
 
 }  // namespace base
 }  // namespace perfetto
-
-
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/temp_file_unittest.cc b/src/base/temp_file_unittest.cc
index c2e2f4d..614894e 100644
--- a/src/base/temp_file_unittest.cc
+++ b/src/base/temp_file_unittest.cc
@@ -17,7 +17,12 @@
 #include "perfetto/ext/base/temp_file.h"
 
 #include <sys/stat.h>
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <unistd.h>
+#endif
 
 #include "test/gtest_and_gmock.h"
 
@@ -60,7 +65,10 @@
   // The file should be deleted and closed now.
   ASSERT_FALSE(PathExists(path));
 
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // Windows UCRT aborts when trying to write into a closed FD.
   ASSERT_EQ(-1, write(fd, "foo", 4));
+#endif
 }
 
 TEST(TempFileTest, CreateUnlinked) {
@@ -72,7 +80,11 @@
     ASSERT_GE(fd, 0);
     ASSERT_GE(write(fd, "foo", 4), 0);
   }
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // Windows UCRT aborts when trying to write into a closed FD.
   ASSERT_EQ(-1, write(fd, "foo", 4));
+#endif
 }
 
 TEST(TempFileTest, ReleaseUnlinked) {
diff --git a/src/base/test/test_task_runner.cc b/src/base/test/test_task_runner.cc
index 7e490d4..a579064 100644
--- a/src/base/test/test_task_runner.cc
+++ b/src/base/test/test_task_runner.cc
@@ -55,9 +55,8 @@
                                         uint32_t timeout_ms) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   if (checkpoints_.count(checkpoint) == 0) {
-    fprintf(stderr, "[TestTaskRunner] Checkpoint \"%s\" does not exist.\n",
-            checkpoint.c_str());
-    abort();
+    PERFETTO_FATAL("[TestTaskRunner] Checkpoint \"%s\" does not exist.\n",
+                   checkpoint.c_str());
   }
   if (checkpoints_[checkpoint])
     return;
@@ -66,9 +65,8 @@
       [this, checkpoint] {
         if (checkpoints_[checkpoint])
           return;
-        fprintf(stderr, "[TestTaskRunner] Failed to reach checkpoint \"%s\"\n",
-                checkpoint.c_str());
-        abort();
+        PERFETTO_FATAL("[TestTaskRunner] Failed to reach checkpoint \"%s\"\n",
+                       checkpoint.c_str());
       },
       timeout_ms);
 
diff --git a/src/base/test/utils.cc b/src/base/test/utils.cc
index 95c6e60..109acf8 100644
--- a/src/base/test/utils.cc
+++ b/src/base/test/utils.cc
@@ -20,10 +20,11 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) ||  \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
 #include <limits.h>
 #include <unistd.h>
@@ -31,11 +32,10 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
     !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
-#include <corecrt_io.h>
 #include <io.h>
 #endif
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #include <mach-o/dyld.h>
 #endif
 
@@ -52,7 +52,7 @@
   PERFETTO_CHECK(size != -1);
   // readlink does not null terminate.
   self_path = std::string(buf, static_cast<size_t>(size));
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   uint32_t size = 0;
   PERFETTO_CHECK(_NSGetExecutablePath(nullptr, &size));
   self_path.resize(size);
@@ -68,10 +68,10 @@
 std::string GetTestDataPath(const std::string& path) {
   std::string self_path = GetCurExecutableDir();
   std::string full_path = self_path + "/../../" + path;
-  if (access(full_path.c_str(), 0 /*F_OK*/) == 0)
+  if (FileExists(full_path))
     return full_path;
   full_path = self_path + "/" + path;
-  if (access(full_path.c_str(), 0 /*F_OK*/) == 0)
+  if (FileExists(full_path))
     return full_path;
   // Fall back to relative to root dir.
   return path;
diff --git a/src/base/test/vm_test_utils.cc b/src/base/test/vm_test_utils.cc
index 2efa845..8e1ba8c 100644
--- a/src/base/test/vm_test_utils.cc
+++ b/src/base/test/vm_test_utils.cc
@@ -36,16 +36,17 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/utils.h"
 
 namespace perfetto {
 namespace base {
 namespace vm_test_utils {
 
 bool IsMapped(void* start, size_t size) {
-  PERFETTO_CHECK(size % kPageSize == 0);
+  const size_t page_size = GetSysPageSize();
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   int retries = 5;
-  int number_of_entries = 4000;  // Just a guess.
+  size_t number_of_entries = 4000;  // Just a guess.
   PSAPI_WORKING_SET_INFORMATION* ws_info = nullptr;
 
   std::vector<char> buffer;
@@ -69,7 +70,7 @@
 
     // Maybe some entries are being added right now. Increase the buffer to
     // take that into account. Increasing by 10% should generally be enough.
-    number_of_entries *= 1.1;
+    number_of_entries = static_cast<size_t>(double(number_of_entries) * 1.1);
 
     PERFETTO_CHECK(--retries > 0);  // If we're looping, eventually fail.
   }
@@ -78,28 +79,28 @@
   // Now scan the working-set information looking for the addresses.
   unsigned pages_found = 0;
   for (unsigned i = 0; i < ws_info->NumberOfEntries; ++i) {
-    void* address =
-        reinterpret_cast<void*>(ws_info->WorkingSetInfo[i].VirtualPage *
-        kPageSize);
+    void* address = reinterpret_cast<void*>(
+        ws_info->WorkingSetInfo[i].VirtualPage * page_size);
     if (address >= start && address < end)
       ++pages_found;
   }
 
-  if (pages_found * kPageSize == size)
+  if (pages_found * page_size == size)
     return true;
   return false;
 #elif PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   // Fuchsia doesn't yet support paging (b/119503290).
+  ignore_result(page_size);
   return true;
 #else
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   using PageState = char;
   static constexpr PageState kIncoreMask = MINCORE_INCORE;
 #else
   using PageState = unsigned char;
   static constexpr PageState kIncoreMask = 1;
 #endif
-  const size_t num_pages = size / kPageSize;
+  const size_t num_pages = (size + page_size - 1) / page_size;
   std::unique_ptr<PageState[]> page_states(new PageState[num_pages]);
   memset(page_states.get(), 0, num_pages * sizeof(PageState));
   int res = mincore(start, size, page_states.get());
diff --git a/src/base/thread_checker_unittest.cc b/src/base/thread_checker_unittest.cc
index f63ec5d..08b7d02 100644
--- a/src/base/thread_checker_unittest.cc
+++ b/src/base/thread_checker_unittest.cc
@@ -16,10 +16,9 @@
 
 #include "perfetto/ext/base/thread_checker.h"
 
-#include <pthread.h>
-
 #include <functional>
 #include <memory>
+#include <thread>
 
 #include "test/gtest_and_gmock.h"
 
@@ -27,39 +26,29 @@
 namespace base {
 namespace {
 
-// We just need two distinct pointers to return to pthread_join().
-void* const kTrue = reinterpret_cast<void*>(1);
-void* const kFalse = nullptr;
-
-void* RunOnThread(std::function<void*(void)> closure) {
-  pthread_t thread;
-  auto thread_main = [](void* arg) -> void* {
-    pthread_exit((*reinterpret_cast<std::function<void*(void)>*>(arg))());
-  };
-  EXPECT_EQ(0, pthread_create(&thread, nullptr, thread_main, &closure));
-  void* retval = nullptr;
-  EXPECT_EQ(0, pthread_join(thread, &retval));
-  return retval;
+bool RunOnThread(std::function<bool(void)> closure) {
+  bool res = false;
+  std::thread thread([&res, &closure] { res = closure(); });
+  thread.join();
+  return res;
 }
 
 TEST(ThreadCheckerTest, Basic) {
   ThreadChecker thread_checker;
   ASSERT_TRUE(thread_checker.CalledOnValidThread());
-  void* res = RunOnThread([&thread_checker]() -> void* {
-    return thread_checker.CalledOnValidThread() ? kTrue : kFalse;
-  });
+  bool res = RunOnThread(
+      [&thread_checker] { return thread_checker.CalledOnValidThread(); });
   ASSERT_TRUE(thread_checker.CalledOnValidThread());
-  ASSERT_EQ(kFalse, res);
+  ASSERT_FALSE(res);
 }
 
 TEST(ThreadCheckerTest, Detach) {
   ThreadChecker thread_checker;
   ASSERT_TRUE(thread_checker.CalledOnValidThread());
   thread_checker.DetachFromThread();
-  void* res = RunOnThread([&thread_checker]() -> void* {
-    return thread_checker.CalledOnValidThread() ? kTrue : kFalse;
-  });
-  ASSERT_EQ(kTrue, res);
+  bool res = RunOnThread(
+      [&thread_checker] { return thread_checker.CalledOnValidThread(); });
+  ASSERT_TRUE(res);
   ASSERT_FALSE(thread_checker.CalledOnValidThread());
 }
 
@@ -68,19 +57,17 @@
   ThreadChecker copied_thread_checker = thread_checker;
   ASSERT_TRUE(thread_checker.CalledOnValidThread());
   ASSERT_TRUE(copied_thread_checker.CalledOnValidThread());
-  void* res = RunOnThread([&copied_thread_checker]() -> void* {
-    return copied_thread_checker.CalledOnValidThread() ? kTrue : kFalse;
+  bool res = RunOnThread([&copied_thread_checker] {
+    return copied_thread_checker.CalledOnValidThread();
   });
-  ASSERT_EQ(kFalse, res);
+  ASSERT_FALSE(res);
 
   copied_thread_checker.DetachFromThread();
-  res = RunOnThread([&thread_checker, &copied_thread_checker]() -> void* {
-    return (copied_thread_checker.CalledOnValidThread() &&
-            !thread_checker.CalledOnValidThread())
-               ? kTrue
-               : kFalse;
+  res = RunOnThread([&thread_checker, &copied_thread_checker] {
+    return copied_thread_checker.CalledOnValidThread() &&
+           !thread_checker.CalledOnValidThread();
   });
-  ASSERT_EQ(kTrue, res);
+  ASSERT_TRUE(res);
 }
 
 }  // namespace
diff --git a/src/base/time.cc b/src/base/time.cc
index 01bf02d..6c45efc 100644
--- a/src/base/time.cc
+++ b/src/base/time.cc
@@ -15,7 +15,9 @@
  */
 
 #include "perfetto/base/time.h"
+
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <Windows.h>
@@ -33,7 +35,8 @@
   ::QueryPerformanceFrequency(&freq);
   LARGE_INTEGER counter;
   ::QueryPerformanceCounter(&counter);
-  double elapsed_nanoseconds = (1e9 * counter.QuadPart) / freq.QuadPart;
+  double elapsed_nanoseconds = (1e9 * static_cast<double>(counter.QuadPart)) /
+                               static_cast<double>(freq.QuadPart);
   return TimeNanos(static_cast<uint64_t>(elapsed_nanoseconds));
 }
 
@@ -65,5 +68,15 @@
 
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
+std::string GetTimeFmt(const std::string& fmt) {
+  time_t raw_time;
+  time(&raw_time);
+  struct tm* local_tm;
+  local_tm = localtime(&raw_time);
+  char buf[128];
+  PERFETTO_CHECK(strftime(buf, 80, fmt.c_str(), local_tm) > 0);
+  return buf;
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/unix_socket.cc b/src/base/unix_socket.cc
index 7db0ff7..1be16cd 100644
--- a/src/base/unix_socket.cc
+++ b/src/base/unix_socket.cc
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
@@ -37,7 +38,7 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #include <sys/ucred.h>
 #endif
 
@@ -46,7 +47,7 @@
 
 // The CMSG_* macros use NULL instead of nullptr.
 #pragma GCC diagnostic push
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
 #endif
 
@@ -54,7 +55,7 @@
 
 // MSG_NOSIGNAL is not supported on Mac OS X, but in that case the socket is
 // created with SO_NOSIGPIPE (See InitializeSocket()).
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 constexpr int kNoSigPipe = 0;
 #else
 constexpr int kNoSigPipe = MSG_NOSIGNAL;
@@ -92,6 +93,8 @@
       return AF_UNIX;
     case SockFamily::kInet:
       return AF_INET;
+    case SockFamily::kInet6:
+      return AF_INET6;
   }
   PERFETTO_CHECK(false);  // For GCC.
 }
@@ -144,6 +147,23 @@
       freeaddrinfo(addr_info);
       return res;
     }
+    case SockFamily::kInet6: {
+      auto parts = SplitString(socket_name, "]");
+      PERFETTO_CHECK(parts.size() == 2);
+      auto address = SplitString(parts[0], "[");
+      PERFETTO_CHECK(address.size() == 1);
+      auto port = SplitString(parts[1], ":");
+      PERFETTO_CHECK(port.size() == 1);
+      struct addrinfo* addr_info = nullptr;
+      struct addrinfo hints {};
+      hints.ai_family = AF_INET6;
+      PERFETTO_CHECK(getaddrinfo(address[0].c_str(), port[0].c_str(), &hints,
+                                 &addr_info) == 0);
+      PERFETTO_CHECK(addr_info->ai_family == AF_INET6);
+      SockaddrAny res(addr_info->ai_addr, addr_info->ai_addrlen);
+      freeaddrinfo(addr_info);
+      return res;
+    }
   }
   PERFETTO_CHECK(false);  // For GCC.
 }
@@ -208,15 +228,19 @@
 UnixSocketRaw::UnixSocketRaw(ScopedFile fd, SockFamily family, SockType type)
     : fd_(std::move(fd)), family_(family), type_(type) {
   PERFETTO_CHECK(fd_);
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   const int no_sigpipe = 1;
   setsockopt(*fd_, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe));
 #endif
 
-  if (family == SockFamily::kInet) {
+  if (family == SockFamily::kInet || family == SockFamily::kInet6) {
     int flag = 1;
     PERFETTO_CHECK(
         !setsockopt(*fd_, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)));
+    flag = 1;
+    // Disable Nagle's algorithm, optimize for low-latency.
+    // See https://github.com/google/perfetto/issues/70.
+    setsockopt(*fd_, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
   }
 
   // There is no reason why a socket should outlive the process in case of
@@ -459,19 +483,21 @@
                                                TaskRunner* task_runner,
                                                SockFamily sock_family,
                                                SockType sock_type) {
-  return std::unique_ptr<UnixSocket>(
-      new UnixSocket(event_listener, task_runner, std::move(fd),
-                     State::kListening, sock_family, sock_type));
+  return std::unique_ptr<UnixSocket>(new UnixSocket(
+      event_listener, task_runner, std::move(fd), State::kListening,
+      sock_family, sock_type, SockPeerCredMode::kReadOnConnect));
 }
 
 // static
-std::unique_ptr<UnixSocket> UnixSocket::Connect(const std::string& socket_name,
-                                                EventListener* event_listener,
-                                                TaskRunner* task_runner,
-                                                SockFamily sock_family,
-                                                SockType sock_type) {
-  std::unique_ptr<UnixSocket> sock(
-      new UnixSocket(event_listener, task_runner, sock_family, sock_type));
+std::unique_ptr<UnixSocket> UnixSocket::Connect(
+    const std::string& socket_name,
+    EventListener* event_listener,
+    TaskRunner* task_runner,
+    SockFamily sock_family,
+    SockType sock_type,
+    SockPeerCredMode peer_cred_mode) {
+  std::unique_ptr<UnixSocket> sock(new UnixSocket(
+      event_listener, task_runner, sock_family, sock_type, peer_cred_mode));
   sock->DoConnect(socket_name);
   return sock;
 }
@@ -482,30 +508,35 @@
     EventListener* event_listener,
     TaskRunner* task_runner,
     SockFamily sock_family,
-    SockType sock_type) {
-  return std::unique_ptr<UnixSocket>(
-      new UnixSocket(event_listener, task_runner, std::move(fd),
-                     State::kConnected, sock_family, sock_type));
+    SockType sock_type,
+    SockPeerCredMode peer_cred_mode) {
+  return std::unique_ptr<UnixSocket>(new UnixSocket(
+      event_listener, task_runner, std::move(fd), State::kConnected,
+      sock_family, sock_type, peer_cred_mode));
 }
 
 UnixSocket::UnixSocket(EventListener* event_listener,
                        TaskRunner* task_runner,
                        SockFamily sock_family,
-                       SockType sock_type)
+                       SockType sock_type,
+                       SockPeerCredMode peer_cred_mode)
     : UnixSocket(event_listener,
                  task_runner,
                  ScopedFile(),
                  State::kDisconnected,
                  sock_family,
-                 sock_type) {}
+                 sock_type,
+                 peer_cred_mode) {}
 
 UnixSocket::UnixSocket(EventListener* event_listener,
                        TaskRunner* task_runner,
                        ScopedFile adopt_fd,
                        State adopt_state,
                        SockFamily sock_family,
-                       SockType sock_type)
-    : event_listener_(event_listener),
+                       SockType sock_type,
+                       SockPeerCredMode peer_cred_mode)
+    : peer_cred_mode_(peer_cred_mode),
+      event_listener_(event_listener),
       task_runner_(task_runner),
       weak_ptr_factory_(this) {
   state_ = State::kDisconnected;
@@ -520,7 +551,8 @@
     PERFETTO_DCHECK(adopt_fd);
     sock_raw_ = UnixSocketRaw(std::move(adopt_fd), sock_family, sock_type);
     state_ = State::kConnected;
-    ReadPeerCredentials();
+    if (peer_cred_mode_ == SockPeerCredMode::kReadOnConnect)
+      ReadPeerCredentials();
   } else if (adopt_state == State::kListening) {
     // We get here from Listen().
 
@@ -605,6 +637,7 @@
   // Peer credentials are supported only on AF_UNIX sockets.
   if (sock_raw_.family() != SockFamily::kUnix)
     return;
+  PERFETTO_CHECK(peer_cred_mode_ != SockPeerCredMode::kIgnore);
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
@@ -642,7 +675,8 @@
     if (res == 0 && sock_err == EINPROGRESS)
       return;  // Not connected yet, just a spurious FD watch wakeup.
     if (res == 0 && sock_err == 0) {
-      ReadPeerCredentials();
+      if (peer_cred_mode_ == SockPeerCredMode::kReadOnConnect)
+        ReadPeerCredentials();
       state_ = State::kConnected;
       return event_listener_->OnConnect(this, true /* connected */);
     }
@@ -665,7 +699,7 @@
         return;
       std::unique_ptr<UnixSocket> new_sock(new UnixSocket(
           event_listener_, task_runner_, std::move(new_fd), State::kConnected,
-          sock_raw_.family(), sock_raw_.type()));
+          sock_raw_.family(), sock_raw_.type(), peer_cred_mode_));
       event_listener_->OnNewIncomingConnection(this, std::move(new_sock));
     }
   }
diff --git a/src/base/unix_socket_unittest.cc b/src/base/unix_socket_unittest.cc
index 2a43200..3039b0f 100644
--- a/src/base/unix_socket_unittest.cc
+++ b/src/base/unix_socket_unittest.cc
@@ -471,6 +471,37 @@
 #endif
 }
 
+// Tests the SockPeerCredMode::kIgnore logic.
+TEST_F(UnixSocketTest, IgnorePeerCredentials) {
+  auto srv = UnixSocket::Listen(kSocketName, &event_listener_, &task_runner_,
+                                SockFamily::kUnix, SockType::kStream);
+  ASSERT_TRUE(srv->is_listening());
+  auto cli1_connected = task_runner_.CreateCheckpoint("cli1_connected");
+  auto cli1 = UnixSocket::Connect(kSocketName, &event_listener_, &task_runner_,
+                                  SockFamily::kUnix, SockType::kStream,
+                                  SockPeerCredMode::kIgnore);
+  EXPECT_CALL(event_listener_, OnConnect(cli1.get(), true))
+      .WillOnce(InvokeWithoutArgs(cli1_connected));
+
+  auto cli2_connected = task_runner_.CreateCheckpoint("cli2_connected");
+  auto cli2 = UnixSocket::Connect(kSocketName, &event_listener_, &task_runner_,
+                                  SockFamily::kUnix, SockType::kStream,
+                                  SockPeerCredMode::kReadOnConnect);
+  EXPECT_CALL(event_listener_, OnConnect(cli2.get(), true))
+      .WillOnce(InvokeWithoutArgs(cli2_connected));
+
+  task_runner_.RunUntilCheckpoint("cli1_connected");
+  task_runner_.RunUntilCheckpoint("cli2_connected");
+
+  ASSERT_EQ(cli1->peer_uid(/*skip_check_for_testing=*/true), kInvalidUid);
+  ASSERT_EQ(cli2->peer_uid(), geteuid());
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  ASSERT_EQ(cli1->peer_pid(/*skip_check_for_testing=*/true), kInvalidPid);
+  ASSERT_EQ(cli2->peer_pid(), getpid());
+#endif
+}
+
 TEST_F(UnixSocketTest, BlockingSend) {
   auto srv = UnixSocket::Listen(kSocketName, &event_listener_, &task_runner_,
                                 SockFamily::kUnix, SockType::kStream);
diff --git a/src/base/unix_task_runner.cc b/src/base/unix_task_runner.cc
index 422947f..f6b6c19 100644
--- a/src/base/unix_task_runner.cc
+++ b/src/base/unix_task_runner.cc
@@ -15,14 +15,20 @@
  */
 
 #include "perfetto/base/build_config.h"
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 
 #include "perfetto/ext/base/unix_task_runner.h"
 
 #include <errno.h>
 #include <stdlib.h>
-#include <unistd.h>
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <synchapi.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <algorithm>
 #include <limits>
 
 #include "perfetto/ext/base/watchdog.h"
@@ -56,13 +62,29 @@
       poll_timeout_ms = GetDelayMsToNextTaskLocked();
       UpdateWatchTasksLocked();
     }
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    DWORD timeout =
+        poll_timeout_ms >= 0 ? static_cast<DWORD>(poll_timeout_ms) : INFINITE;
+    DWORD ret =
+        WaitForMultipleObjects(static_cast<DWORD>(poll_fds_.size()),
+                               &poll_fds_[0], /*bWaitAll=*/false, timeout);
+    // Unlike poll(2), WaitForMultipleObjects() returns only *one* handle in the
+    // set, even when >1 is signalled. In order to avoid starvation,
+    // PostFileDescriptorWatches() will WaitForSingleObject() each other handle
+    // to ensure fairness. |ret| here is passed just to avoid an extra
+    // WaitForSingleObject() for the one handle that WaitForMultipleObject()
+    // returned.
+    PostFileDescriptorWatches(ret);
+#else
     int ret = PERFETTO_EINTR(poll(
         &poll_fds_[0], static_cast<nfds_t>(poll_fds_.size()), poll_timeout_ms));
     PERFETTO_CHECK(ret >= 0);
+    PostFileDescriptorWatches(0 /*ignored*/);
+#endif
 
     // To avoid starvation we always interleave all types of tasks -- immediate,
     // delayed and file descriptor watches.
-    PostFileDescriptorWatches();
     RunImmediateAndDelayedTask();
   }
 }
@@ -85,13 +107,22 @@
 
 void UnixTaskRunner::UpdateWatchTasksLocked() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   if (!watch_tasks_changed_)
     return;
   watch_tasks_changed_ = false;
+#endif
   poll_fds_.clear();
   for (auto& it : watch_tasks_) {
-    it.second.poll_fd_index = poll_fds_.size();
-    poll_fds_.push_back({it.first, POLLIN | POLLHUP, 0});
+    PlatformHandle handle = it.first;
+    WatchTask& watch_task = it.second;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    if (!watch_task.pending)
+      poll_fds_.push_back(handle);
+#else
+    watch_task.poll_fd_index = poll_fds_.size();
+    poll_fds_.push_back({handle, POLLIN | POLLHUP, 0});
+#endif
   }
 }
 
@@ -123,47 +154,81 @@
     RunTaskWithWatchdogGuard(delayed_task);
 }
 
-void UnixTaskRunner::PostFileDescriptorWatches() {
+void UnixTaskRunner::PostFileDescriptorWatches(uint64_t windows_wait_result) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   for (size_t i = 0; i < poll_fds_.size(); i++) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    const PlatformHandle handle = poll_fds_[i];
+    // |windows_wait_result| is the result of WaitForMultipleObjects() call. If
+    // one of the objects was signalled, it will have a value between
+    // [0, poll_fds_.size()].
+    if (i != windows_wait_result &&
+        WaitForSingleObject(handle, 0) != WAIT_OBJECT_0) {
+      continue;
+    }
+#else
+    base::ignore_result(windows_wait_result);
+    const PlatformHandle handle = poll_fds_[i].fd;
     if (!(poll_fds_[i].revents & (POLLIN | POLLHUP)))
       continue;
     poll_fds_[i].revents = 0;
+#endif
 
     // The wake-up event is handled inline to avoid an infinite recursion of
     // posted tasks.
-    if (poll_fds_[i].fd == event_.fd()) {
+    if (handle == event_.fd()) {
       event_.Clear();
       continue;
     }
 
     // Binding to |this| is safe since we are the only object executing the
     // task.
-    PostTask(std::bind(&UnixTaskRunner::RunFileDescriptorWatch, this,
-                       poll_fds_[i].fd));
+    PostTask(std::bind(&UnixTaskRunner::RunFileDescriptorWatch, this, handle));
 
-    // Make the fd negative while a posted task is pending. This makes poll(2)
-    // ignore the fd.
+    // Flag the task as pending.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    // On Windows this is done by marking the WatchTask entry as pending. This
+    // is more expensive than Linux as requires rebuilding the |poll_fds_|
+    // vector on each call. There doesn't seem to be a good alternative though.
+    auto it = watch_tasks_.find(handle);
+    PERFETTO_CHECK(it != watch_tasks_.end());
+    PERFETTO_DCHECK(!it->second.pending);
+    it->second.pending = true;
+#else
+    // On UNIX systems instead, we just make the fd negative while its task is
+    // pending. This makes poll(2) ignore the fd.
     PERFETTO_DCHECK(poll_fds_[i].fd >= 0);
     poll_fds_[i].fd = -poll_fds_[i].fd;
+#endif
   }
 }
 
-void UnixTaskRunner::RunFileDescriptorWatch(int fd) {
+void UnixTaskRunner::RunFileDescriptorWatch(PlatformHandle fd) {
   std::function<void()> task;
   {
     std::lock_guard<std::mutex> lock(lock_);
     auto it = watch_tasks_.find(fd);
     if (it == watch_tasks_.end())
       return;
+    WatchTask& watch_task = it->second;
+
     // Make poll(2) pay attention to the fd again. Since another thread may have
     // updated this watch we need to refresh the set first.
     UpdateWatchTasksLocked();
-    size_t fd_index = it->second.poll_fd_index;
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    // On Windows we manually track the presence of outstanding tasks for the
+    // watch. The UpdateWatchTasksLocked() in the Run() loop will re-add the
+    // task to the |poll_fds_| vector.
+    PERFETTO_DCHECK(watch_task.pending);
+    watch_task.pending = false;
+#else
+    size_t fd_index = watch_task.poll_fd_index;
     PERFETTO_DCHECK(fd_index < poll_fds_.size());
     PERFETTO_DCHECK(::abs(poll_fds_[fd_index].fd) == fd);
     poll_fds_[fd_index].fd = fd;
-    task = it->second.callback;
+#endif
+    task = watch_task.callback;
   }
   errno = 0;
   RunTaskWithWatchdogGuard(task);
@@ -201,20 +266,26 @@
   WakeUp();
 }
 
-void UnixTaskRunner::AddFileDescriptorWatch(int fd,
+void UnixTaskRunner::AddFileDescriptorWatch(PlatformHandle fd,
                                             std::function<void()> task) {
-  PERFETTO_DCHECK(fd >= 0);
+  PERFETTO_DCHECK(PlatformHandleChecker::IsValid(fd));
   {
     std::lock_guard<std::mutex> lock(lock_);
     PERFETTO_DCHECK(!watch_tasks_.count(fd));
-    watch_tasks_[fd] = {std::move(task), SIZE_MAX};
+    WatchTask& watch_task = watch_tasks_[fd];
+    watch_task.callback = std::move(task);
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    watch_task.pending = false;
+#else
+    watch_task.poll_fd_index = SIZE_MAX;
+#endif
     watch_tasks_changed_ = true;
   }
   WakeUp();
 }
 
-void UnixTaskRunner::RemoveFileDescriptorWatch(int fd) {
-  PERFETTO_DCHECK(fd >= 0);
+void UnixTaskRunner::RemoveFileDescriptorWatch(PlatformHandle fd) {
+  PERFETTO_DCHECK(PlatformHandleChecker::IsValid(fd));
   {
     std::lock_guard<std::mutex> lock(lock_);
     PERFETTO_DCHECK(watch_tasks_.count(fd));
@@ -230,5 +301,3 @@
 
 }  // namespace base
 }  // namespace perfetto
-
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/base/utils.cc b/src/base/utils.cc
new file mode 100644
index 0000000..1f66a38
--- /dev/null
+++ b/src/base/utils.cc
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/base/utils.h"
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <unistd.h>  // For getpagesize().
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+#include <mach/vm_page_size.h>
+#endif
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <dlfcn.h>
+#include <malloc.h>
+
+#ifdef M_PURGE
+#define PERFETTO_M_PURGE M_PURGE
+#else
+// Only available in in-tree builds and on newer SDKs.
+#define PERFETTO_M_PURGE -101
+#endif
+
+namespace {
+extern "C" {
+using MalloptType = void (*)(int, int);
+}
+}  // namespace
+#endif  // OS_ANDROID
+
+namespace perfetto {
+namespace base {
+
+void MaybeReleaseAllocatorMemToOS() {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  // mallopt() on Android requires SDK level 26. Many targets and embedders
+  // still depend on a lower SDK level. Given mallopt() is a quite simple API,
+  // use reflection to do this rather than bumping the SDK level for all
+  // embedders. This keeps the behavior of standalone builds aligned with
+  // in-tree builds.
+  static MalloptType mallopt_fn =
+      reinterpret_cast<MalloptType>(dlsym(RTLD_DEFAULT, "mallopt"));
+  if (!mallopt_fn)
+    return;
+  mallopt_fn(PERFETTO_M_PURGE, 0);
+#endif
+}
+
+uint32_t GetSysPageSize() {
+  ignore_result(kPageSize);  // Just to keep the amalgamated build happy.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  static std::atomic<uint32_t> page_size{0};
+  // This function might be called in hot paths. Avoid calling getpagesize() all
+  // the times, in many implementations getpagesize() calls sysconf() which is
+  // not cheap.
+  uint32_t cached_value = page_size.load(std::memory_order_relaxed);
+  if (PERFETTO_UNLIKELY(cached_value == 0)) {
+    cached_value = static_cast<uint32_t>(getpagesize());
+    page_size.store(cached_value, std::memory_order_relaxed);
+  }
+  return cached_value;
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  return static_cast<uint32_t>(vm_page_size);
+#else
+  return 4096;
+#endif
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/utils_unittest.cc b/src/base/utils_unittest.cc
index eade8f7..ef6e657 100644
--- a/src/base/utils_unittest.cc
+++ b/src/base/utils_unittest.cc
@@ -16,13 +16,25 @@
 
 #include "perfetto/ext/base/utils.h"
 
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#else
 #include <fcntl.h>
 #include <signal.h>
-#include <stdint.h>
 #include <unistd.h>
+#endif
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <random>
+#include <thread>
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/temp_file.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -58,9 +70,82 @@
   EXPECT_EQ(4u, ArraySize(bar_4));
 }
 
+TEST(UtilsTest, PipeBlockingRW) {
+  Pipe pipe = Pipe::Create();
+  std::string expected;
+  expected.resize(1024 * 512u);
+  for (size_t i = 0; i < expected.size(); i++)
+    expected[i] = '!' + static_cast<char>(i % 64);
+
+  std::thread writer([&] {
+    std::string tx = expected;
+    std::minstd_rand0 rnd_engine(0);
+
+    while (!tx.empty()) {
+      size_t wsize = static_cast<size_t>(rnd_engine() % 4096) + 1;
+      wsize = std::min(wsize, tx.size());
+      WriteAllHandle(*pipe.wr, &tx[0], wsize);
+      tx.erase(0, wsize);
+    }
+    pipe.wr.reset();
+  });
+
+  std::string actual;
+  ASSERT_TRUE(ReadPlatformHandle(*pipe.rd, &actual));
+  ASSERT_EQ(actual, expected);
+  writer.join();
+}
+
+// Tests that WriteAllHandle and ReadPlatformHandle work as advertised.
+// TODO(primiano): normalize File handling on Windows. Right now some places
+// use POSIX-compat APIs that use "int" file descriptors (_open, _read, _write),
+// some other places use WINAPI files (CreateFile(), ReadFile()), where the file
+// is a HANDLE.
+TEST(UtilsTest, ReadWritePlatformHandle) {
+  auto tmp = TempDir::Create();
+  std::string payload = "foo\nbar\0baz\r\nqux";
+  std::string tmp_path = tmp.path() + "/temp.txt";
+
+  // Write a file using PlatformHandle. Note: the {} blocks are to make sure
+  // that the file is automatically closed via RAII before being reopened.
+  {
+    ScopedPlatformHandle handle {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+      ::CreateFileA(tmp_path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
+                    FILE_ATTRIBUTE_NORMAL, nullptr)
+#else
+      OpenFile(tmp_path, O_WRONLY | O_CREAT | O_TRUNC, 0644)
+#endif
+    };
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(WriteAllHandle(*handle, payload.data(), payload.size()),
+              static_cast<ssize_t>(payload.size()));
+  }
+
+  // Read it back.
+  {
+    ScopedPlatformHandle handle {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+      ::CreateFileA(tmp_path.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING,
+                    FILE_ATTRIBUTE_NORMAL, nullptr)
+#else
+      OpenFile(tmp_path, O_RDONLY)
+#endif
+    };
+    ASSERT_TRUE(handle);
+    std::string actual = "preserve_existing:";
+    ASSERT_TRUE(ReadPlatformHandle(*handle, &actual));
+    ASSERT_EQ(actual, "preserve_existing:" + payload);
+  }
+
+  ASSERT_EQ(remove(tmp_path.c_str()), 0);
+}
+
 // Fuchsia doesn't currently support sigaction(), see
 // fuchsia.atlassian.net/browse/ZX-560.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 TEST(UtilsTest, EintrWrapper) {
   Pipe pipe = Pipe::Create();
 
@@ -101,7 +186,7 @@
   // Restore the old handler.
   sigaction(SIGUSR2, &old_sa, nullptr);
 }
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
+#endif  // LINUX | ANDROID | APPLE
 
 TEST(UtilsTest, Align) {
   EXPECT_EQ(0u, AlignUp<4>(0));
diff --git a/src/base/version.cc b/src/base/version.cc
new file mode 100644
index 0000000..18569d3
--- /dev/null
+++ b/src/base/version.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/base/version.h"
+
+#include "perfetto/base/build_config.h"
+
+#include <stdio.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
+#include "perfetto_version.gen.h"
+#else
+#define PERFETTO_VERSION_STRING() "v0.0"
+#define PERFETTO_VERSION_SCM_REVISION() "unknown"
+#endif
+
+namespace perfetto {
+namespace base {
+
+const char* GetVersionString() {
+  static const char* version_str = [] {
+    static constexpr size_t kMaxLen = 256;
+    char* version = new char[kMaxLen + 1];
+    snprintf(version, kMaxLen, "Perfetto %s (%s)", PERFETTO_VERSION_STRING(),
+             PERFETTO_VERSION_SCM_REVISION());
+    return version;
+  }();
+  return version_str;
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/watchdog_posix.cc b/src/base/watchdog_posix.cc
index 2f251b7..35d8974 100644
--- a/src/base/watchdog_posix.cc
+++ b/src/base/watchdog_posix.cc
@@ -29,7 +29,9 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/thread_utils.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/utils.h"
 
 namespace perfetto {
 namespace base {
@@ -70,7 +72,7 @@
   c[c_pos] = '\0';
 
   if (sscanf(c,
-             "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
+             "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu "
              "%lu %*d %*d %*d %*d %*d %*d %*u %*u %ld",
              &out->utime, &out->stime, &out->rss_pages) != 3) {
     PERFETTO_ELOG("Invalid stat format: %s", c);
@@ -167,7 +169,7 @@
 
     uint64_t cpu_time = stat.utime + stat.stime;
     uint64_t rss_bytes =
-        static_cast<uint64_t>(stat.rss_pages) * base::kPageSize;
+        static_cast<uint64_t>(stat.rss_pages) * base::GetSysPageSize();
 
     CheckMemory(rss_bytes);
     CheckCpu(cpu_time);
diff --git a/src/base/watchdog_posix_unittest.cc b/src/base/watchdog_posix_unittest.cc
new file mode 100644
index 0000000..52bd49d
--- /dev/null
+++ b/src/base/watchdog_posix_unittest.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
+
+#include "perfetto/ext/base/watchdog_posix.h"
+
+#include <stdio.h>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+namespace {
+
+TEST(WatchdogPosixTest, ParseProcStat) {
+  constexpr const char stat[] =
+      "2965981 (zsh) S 2965977 2965981 2965981 34822 2966607 4194304 6632 6697 "
+      "0 0 11 6 4 1 20 0 1 0 227163466 15839232 2311 18446744073709551615 "
+      "94823961161728 94823961762781 140722993535472 0 0 0 2 3686400 134295555 "
+      "0 0 0 17 2 0 0 0 0 0 94823961905904 94823961935208 94823993954304 "
+      "140722993543678 140722993543691 140722993543691 140722993545195 0";
+  TempFile f = TempFile::CreateUnlinked();
+  WriteAll(f.fd(), stat, sizeof(stat));
+  ASSERT_NE(lseek(f.fd(), 0, SEEK_SET), -1);
+  ProcStat ps;
+  ASSERT_TRUE(ReadProcStat(f.fd(), &ps));
+  EXPECT_EQ(ps.utime, 11u);
+  EXPECT_EQ(ps.stime, 6u);
+  EXPECT_EQ(ps.rss_pages, 2311);
+}
+
+}  // namespace
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
diff --git a/src/ipc/BUILD.gn b/src/ipc/BUILD.gn
index d8ee2b4..39843f1 100644
--- a/src/ipc/BUILD.gn
+++ b/src/ipc/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("../../gn/fuzzer.gni")
 import("../../gn/perfetto.gni")
+import("../../gn/perfetto_component.gni")
 import("../../gn/proto_library.gni")
 import("../../gn/test.gni")
 
@@ -21,6 +22,15 @@
 # projects should be depending on our IPC layer.
 assert(enable_perfetto_ipc)
 
+if (perfetto_component_type == "static_library") {
+  # In build generators everything else outside should just depend on the
+  # :perfetto_ipc static library (at the end of this file) rather than
+  # individual sub-targets.
+  _ipc_visibility = [ ":*" ]  # Only targets defined in this file.
+} else {
+  _ipc_visibility = [ "*" ]  # Default visibility.
+}
+
 source_set("client") {
   public_deps = [
     "../../include/perfetto/ext/ipc",
@@ -37,6 +47,7 @@
     "client_impl.h",
     "service_proxy.cc",
   ]
+  visibility = _ipc_visibility
 }
 
 source_set("host") {
@@ -54,6 +65,7 @@
     "host_impl.cc",
     "host_impl.h",
   ]
+  visibility = _ipc_visibility
 }
 
 source_set("common") {
@@ -71,6 +83,7 @@
     "deferred.cc",
     "virtual_destructors.cc",
   ]
+  visibility = _ipc_visibility
 }
 
 perfetto_fuzzer_test("buffered_frame_deserializer_fuzzer") {
@@ -119,10 +132,14 @@
   ]
 }
 
-# This is used by Bazel BUILD rules.
-static_library("perfetto_ipc") {
-  complete_static_lib = true
-  deps = [
+# This is used by Bazel BUILD rules. The problem it solves is the following:
+# In GN builds we want to keep client and host separate. This allows reducing
+# the binary size by splitting targets all the way up to the client library
+# (libperfetto_client_experimental). In bazel/blaze we fuse everything together
+# because handling this split is too complex due to the lack of a
+# source_set-equivalent.
+perfetto_component("perfetto_ipc") {
+  public_deps = [
     ":client",
     ":host",
     "../../gn:default_deps",
diff --git a/src/ipc/buffered_frame_deserializer.cc b/src/ipc/buffered_frame_deserializer.cc
index 9ef22eb..d893dfb 100644
--- a/src/ipc/buffered_frame_deserializer.cc
+++ b/src/ipc/buffered_frame_deserializer.cc
@@ -38,8 +38,8 @@
 
 BufferedFrameDeserializer::BufferedFrameDeserializer(size_t max_capacity)
     : capacity_(max_capacity) {
-  PERFETTO_CHECK(max_capacity % base::kPageSize == 0);
-  PERFETTO_CHECK(max_capacity > base::kPageSize);
+  PERFETTO_CHECK(max_capacity % base::GetSysPageSize() == 0);
+  PERFETTO_CHECK(max_capacity >= base::GetSysPageSize());
 }
 
 BufferedFrameDeserializer::~BufferedFrameDeserializer() = default;
@@ -56,7 +56,8 @@
 
     // Surely we are going to use at least the first page, but we may not need
     // the rest for a bit.
-    buf_.AdviseDontNeed(buf() + base::kPageSize, capacity_ - base::kPageSize);
+    const auto page_size = base::GetSysPageSize();
+    buf_.AdviseDontNeed(buf() + page_size, capacity_ - page_size);
   }
 
   PERFETTO_CHECK(capacity_ > size_);
@@ -64,6 +65,7 @@
 }
 
 bool BufferedFrameDeserializer::EndReceive(size_t recv_size) {
+  const auto page_size = base::GetSysPageSize();
   PERFETTO_CHECK(recv_size + size_ <= capacity_);
   size_ += recv_size;
 
@@ -139,8 +141,8 @@
     // If we just finished decoding a large frame that used more than one page,
     // release the extra memory in the buffer. Large frames should be quite
     // rare.
-    if (consumed_size > base::kPageSize) {
-      size_t size_rounded_up = (size_ / base::kPageSize + 1) * base::kPageSize;
+    if (consumed_size > page_size) {
+      size_t size_rounded_up = (size_ / page_size + 1) * page_size;
       if (size_rounded_up < capacity_) {
         char* madvise_begin = buf() + size_rounded_up;
         const size_t madvise_size = capacity_ - size_rounded_up;
diff --git a/src/ipc/client_impl.cc b/src/ipc/client_impl.cc
index c569eb4..26106b1 100644
--- a/src/ipc/client_impl.cc
+++ b/src/ipc/client_impl.cc
@@ -23,6 +23,7 @@
 #include <utility>
 
 #include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/ipc/service_descriptor.h"
 #include "perfetto/ext/ipc/service_proxy.h"
@@ -37,17 +38,29 @@
 namespace ipc {
 
 // static
-std::unique_ptr<Client> Client::CreateInstance(const char* socket_name,
+std::unique_ptr<Client> Client::CreateInstance(ConnArgs conn_args,
                                                base::TaskRunner* task_runner) {
-  std::unique_ptr<Client> client(new ClientImpl(socket_name, task_runner));
+  std::unique_ptr<Client> client(
+      new ClientImpl(std::move(conn_args), task_runner));
   return client;
 }
 
-ClientImpl::ClientImpl(const char* socket_name, base::TaskRunner* task_runner)
-    : task_runner_(task_runner), weak_ptr_factory_(this) {
-  sock_ = base::UnixSocket::Connect(socket_name, this, task_runner,
-                                    base::SockFamily::kUnix,
-                                    base::SockType::kStream);
+ClientImpl::ClientImpl(ConnArgs conn_args, base::TaskRunner* task_runner)
+    : socket_name_(conn_args.socket_name),
+      socket_retry_(conn_args.retry),
+      task_runner_(task_runner),
+      weak_ptr_factory_(this) {
+  if (conn_args.socket_fd) {
+    // Create the client using a connected socket. This code path will never hit
+    // OnConnect().
+    sock_ = base::UnixSocket::AdoptConnected(
+        std::move(conn_args.socket_fd), this, task_runner_,
+        base::SockFamily::kUnix, base::SockType::kStream,
+        base::SockPeerCredMode::kIgnore);
+  } else {
+    // Connect using the socket name.
+    TryConnect();
+  }
 }
 
 ClientImpl::~ClientImpl() {
@@ -57,6 +70,13 @@
       nullptr);  // The base::UnixSocket* ptr is not used in OnDisconnect().
 }
 
+void ClientImpl::TryConnect() {
+  PERFETTO_DCHECK(socket_name_);
+  sock_ = base::UnixSocket::Connect(
+      socket_name_, this, task_runner_, base::SockFamily::kUnix,
+      base::SockType::kStream, base::SockPeerCredMode::kIgnore);
+}
+
 void ClientImpl::BindService(base::WeakPtr<ServiceProxy> service_proxy) {
   if (!service_proxy)
     return;
@@ -129,16 +149,35 @@
 }
 
 void ClientImpl::OnConnect(base::UnixSocket*, bool connected) {
-  // Drain the BindService() calls that were queued before establishig the
-  // connection with the host.
-  for (base::WeakPtr<ServiceProxy>& service_proxy : queued_bindings_) {
+  if (!connected && socket_retry_) {
+    socket_backoff_ms_ =
+        (socket_backoff_ms_ < 10000) ? socket_backoff_ms_ + 1000 : 30000;
+    PERFETTO_DLOG(
+        "Connection to traced's UNIX socket failed, retrying in %u seconds",
+        socket_backoff_ms_ / 1000);
+    auto weak_this = weak_ptr_factory_.GetWeakPtr();
+    task_runner_->PostDelayedTask(
+        [weak_this] {
+          if (weak_this)
+            static_cast<ClientImpl&>(*weak_this).TryConnect();
+        },
+        socket_backoff_ms_);
+    return;
+  }
+
+  // Drain the BindService() calls that were queued before establishing the
+  // connection with the host. Note that if we got disconnected, the call to
+  // OnConnect below might delete |this|, so move everything on the stack first.
+  auto queued_bindings = std::move(queued_bindings_);
+  queued_bindings_.clear();
+  for (base::WeakPtr<ServiceProxy>& service_proxy : queued_bindings) {
     if (connected) {
       BindService(service_proxy);
     } else if (service_proxy) {
       service_proxy->OnConnect(false /* success */);
     }
   }
-  queued_bindings_.clear();
+  // Don't access |this| below here.
 }
 
 void ClientImpl::OnDisconnect(base::UnixSocket*) {
diff --git a/src/ipc/client_impl.h b/src/ipc/client_impl.h
index ff9c7bc..43e6045 100644
--- a/src/ipc/client_impl.h
+++ b/src/ipc/client_impl.h
@@ -46,7 +46,7 @@
 
 class ClientImpl : public Client, public base::UnixSocket::EventListener {
  public:
-  ClientImpl(const char* socket_name, base::TaskRunner*);
+  ClientImpl(ConnArgs, base::TaskRunner*);
   ~ClientImpl() override;
 
   // Client implementation.
@@ -67,6 +67,8 @@
                         base::WeakPtr<ServiceProxy>,
                         int fd = -1);
 
+  base::UnixSocket* GetUnixSocketForTesting() { return sock_.get(); }
+
  private:
   struct QueuedRequest {
     QueuedRequest();
@@ -81,6 +83,7 @@
   ClientImpl(const ClientImpl&) = delete;
   ClientImpl& operator=(const ClientImpl&) = delete;
 
+  void TryConnect();
   bool SendFrame(const Frame&, int fd = -1);
   void OnFrameReceived(const Frame&);
   void OnBindServiceReply(QueuedRequest,
@@ -89,6 +92,9 @@
                            const protos::gen::IPCFrame_InvokeMethodReply&);
 
   bool invoking_method_reply_ = false;
+  const char* socket_name_ = nullptr;
+  bool socket_retry_ = false;
+  uint32_t socket_backoff_ms_ = 0;
   std::unique_ptr<base::UnixSocket> sock_;
   base::TaskRunner* const task_runner_;
   RequestID last_request_id_ = 0;
diff --git a/src/ipc/client_impl_unittest.cc b/src/ipc/client_impl_unittest.cc
index 183fbf0..6aa8b33 100644
--- a/src/ipc/client_impl_unittest.cc
+++ b/src/ipc/client_impl_unittest.cc
@@ -205,7 +205,8 @@
   void SetUp() override {
     task_runner_.reset(new base::TestTaskRunner());
     host_.reset(new FakeHost(task_runner_.get()));
-    cli_ = Client::CreateInstance(kSockName, task_runner_.get());
+    cli_ = Client::CreateInstance({kSockName, /*retry=*/false},
+                                  task_runner_.get());
   }
 
   void TearDown() override {
@@ -575,6 +576,28 @@
   task_runner_->RunUntilCheckpoint("on_disconnect");
 }
 
+TEST_F(ClientImplTest, HostConnectionFailure) {
+  constexpr char kNonexistentSockName[] =
+      TEST_SOCK_NAME("client_impl_unittest_nonexistent");
+  std::unique_ptr<Client> client = Client::CreateInstance(
+      {kNonexistentSockName, /*retry=*/false}, task_runner_.get());
+
+  // Connect a client to a non-existent socket, which will always fail. The
+  // client will notify the proxy of disconnection.
+  std::unique_ptr<FakeProxy> proxy(new FakeProxy("FakeSvc", &proxy_events_));
+  client->BindService(proxy->GetWeakPtr());
+
+  // Make sure the client copes with being deleted by the disconnection
+  // callback.
+  auto on_disconnect_reached = task_runner_->CreateCheckpoint("on_disconnect");
+  auto on_disconnect = [&] {
+    client.reset();
+    on_disconnect_reached();
+  };
+  EXPECT_CALL(proxy_events_, OnDisconnect()).WillOnce(Invoke(on_disconnect));
+  task_runner_->RunUntilCheckpoint("on_disconnect");
+}
+
 // TODO(primiano): add the tests below.
 // TEST(ClientImplTest, UnparsableReply) {}
 
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index 2806a53..c6cf8c1 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -92,6 +92,8 @@
   clients_by_socket_[new_conn.get()] = client.get();
   client->id = client_id;
   client->sock = std::move(new_conn);
+  // Watchdog is 30 seconds, so set the socket timeout to 10 seconds.
+  client->sock->SetTxTimeout(10000);
   clients_[client_id] = std::move(client);
 }
 
@@ -232,11 +234,14 @@
 void HostImpl::SendFrame(ClientConnection* client, const Frame& frame, int fd) {
   std::string buf = BufferedFrameDeserializer::Serialize(frame);
 
-  // TODO(primiano): this should do non-blocking I/O. But then what if the
-  // socket buffer is full? We might want to either drop the request or throttle
-  // the send and PostTask the reply later? Right now we are making Send()
-  // blocking as a workaround. Propagate bakpressure to the caller instead.
+  // When a new Client connects in OnNewClientConnection we set a timeout on
+  // Send (see call to SetTxTimeout).
+  //
+  // The old behaviour was to do a blocking I/O call, which caused crashes from
+  // misbehaving producers (see b/169051440).
   bool res = client->sock->Send(buf.data(), buf.size(), fd);
+  // If we timeout |res| will be false, but the UnixSocket will have called
+  // UnixSocket::ShutDown() and thus |is_connected()| is false.
   PERFETTO_CHECK(res || !client->sock->is_connected());
 }
 
diff --git a/src/ipc/test/ipc_integrationtest.cc b/src/ipc/test/ipc_integrationtest.cc
index b681f95..b2f5bc5 100644
--- a/src/ipc/test/ipc_integrationtest.cc
+++ b/src/ipc/test/ipc_integrationtest.cc
@@ -81,7 +81,7 @@
   auto on_connect = task_runner_.CreateCheckpoint("on_connect");
   EXPECT_CALL(svc_proxy_events_, OnConnect()).WillOnce(Invoke(on_connect));
   std::unique_ptr<Client> cli =
-      Client::CreateInstance(kSockName, &task_runner_);
+      Client::CreateInstance({kSockName, /*retry=*/false}, &task_runner_);
   std::unique_ptr<GreeterProxy> svc_proxy(new GreeterProxy(&svc_proxy_events_));
   cli->BindService(svc_proxy->GetWeakPtr());
   task_runner_.RunUntilCheckpoint("on_connect");
diff --git a/src/traced/probes/ftrace/kallsyms/BUILD.gn b/src/kallsyms/BUILD.gn
similarity index 68%
rename from src/traced/probes/ftrace/kallsyms/BUILD.gn
rename to src/kallsyms/BUILD.gn
index 0c42b98..5f305f8 100644
--- a/src/traced/probes/ftrace/kallsyms/BUILD.gn
+++ b/src/kallsyms/BUILD.gn
@@ -12,17 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("../../../../../gn/test.gni")
+import("../../gn/test.gni")
 
 source_set("kallsyms") {
   deps = [
-    "../../../../../gn:default_deps",
-    "../../../../../include/perfetto/protozero",
-    "../../../../base",
+    "../../gn:default_deps",
+    "../../include/perfetto/protozero",
+    "../base",
   ]
   sources = [
     "kernel_symbol_map.cc",
     "kernel_symbol_map.h",
+    "lazy_kernel_symbolizer.cc",
+    "lazy_kernel_symbolizer.h",
   ]
 }
 
@@ -30,11 +32,14 @@
   testonly = true
   deps = [
     ":kallsyms",
-    "../../../../../gn:default_deps",
-    "../../../../../gn:gtest_and_gmock",
-    "../../../../base",
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../base",
   ]
-  sources = [ "kernel_symbol_map_unittest.cc" ]
+  sources = [
+    "kernel_symbol_map_unittest.cc",
+    "lazy_kernel_symbolizer_unittest.cc",
+  ]
 }
 
 if (enable_perfetto_benchmarks) {
@@ -42,10 +47,10 @@
     testonly = true
     deps = [
       ":kallsyms",
-      "../../../../../gn:benchmark",
-      "../../../../../gn:default_deps",
-      "../../../../base",
-      "../../../../base:test_support",
+      "../../gn:benchmark",
+      "../../gn:default_deps",
+      "../base",
+      "../base:test_support",
     ]
     sources = [ "kernel_symbol_map_benchmark.cc" ]
   }
diff --git a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map.cc b/src/kallsyms/kernel_symbol_map.cc
similarity index 82%
rename from src/traced/probes/ftrace/kallsyms/kernel_symbol_map.cc
rename to src/kallsyms/kernel_symbol_map.cc
index f2d55f0..920c897 100644
--- a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map.cc
+++ b/src/kallsyms/kernel_symbol_map.cc
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-#include "src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/kernel_symbol_map.h"
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/paged_memory.h"
 #include "perfetto/ext/base/scoped_file.h"
@@ -43,6 +45,7 @@
 
 using TokenId = KernelSymbolMap::TokenTable::TokenId;
 constexpr size_t kSymNameMaxLen = 128;
+constexpr size_t kSymMaxSizeBytes = 1024 * 1024;
 
 // Reads a kallsyms file in blocks of 4 pages each and decode its lines using
 // a simple FSM. Calls the passed lambda for each valid symbol.
@@ -74,7 +77,7 @@
   size_t sym_name_len = 0;
   for (;;) {
     char* buf = static_cast<char*>(buffer.Get());
-    auto rsize = PERFETTO_EINTR(read(*fd, buf, kBufSize));
+    auto rsize = base::Read(*fd, buf, kBufSize);
     if (rsize < 0) {
       PERFETTO_PLOG("read(%s) failed", kallsyms_path.c_str());
       return;
@@ -198,7 +201,7 @@
     PERFETTO_DCHECK((token.at(i) & 0x80) == 0);  // |token| must be ASCII only.
     *(tok_wptr++) = token.at(i) & 0x7f;
   }
-  *(tok_wptr++) = token.at(token_size - 1) | 0x80;
+  *(tok_wptr++) = static_cast<char>(token.at(token_size - 1) | 0x80);
   PERFETTO_DCHECK(tok_wptr == &buf_[buf_.size()]);
   return id;
 }
@@ -232,7 +235,7 @@
 }
 
 size_t KernelSymbolMap::Parse(const std::string& kallsyms_path) {
-  PERFETTO_METATRACE_SCOPED(TAG_FTRACE, KALLSYMS_PARSE);
+  PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, KALLSYMS_PARSE);
   using SymAddr = uint64_t;
 
   struct TokenInfo {
@@ -246,7 +249,18 @@
   TokenMap tokens;
 
   // Keep the (ordered) list of tokens for each symbol.
-  std::multimap<SymAddr, TokenMapPtr> symbols;
+  struct SymAddrAndTokenPtr {
+    SymAddr addr;
+    TokenMapPtr token_map_entry;
+
+    bool operator<(const SymAddrAndTokenPtr& other) const {
+      return addr < other.addr;
+    }
+  };
+  std::vector<SymAddrAndTokenPtr> symbol_tokens;
+
+  // Based on `cat /proc/kallsyms | egrep "\b[tT]\b" | wc -l`.
+  symbol_tokens.reserve(128 * 1024);
 
   ForEachSym(kallsyms_path, [&](SymAddr addr, char type, const char* name) {
     if (addr == 0 || (type != 't' && type != 'T') || name[0] == '$') {
@@ -257,16 +271,27 @@
     // "foo_bar" -> ["foo", "bar"]). For each token hash:
     // 1. Keep track of the frequency of each token.
     // 2. Keep track of the list of token hashes for each symbol.
-    Tokenize(name, [&tokens, &symbols, addr](base::StringView token) {
+    Tokenize(name, [&tokens, &symbol_tokens, addr](base::StringView token) {
       // Strip the .cfi part if present.
       if (token.substr(token.size() - 4) == ".cfi")
         token = token.substr(0, token.size() - 4);
       auto it_and_ins = tokens.emplace(token.ToStdString(), TokenInfo{});
       it_and_ins.first->second.count++;
-      symbols.emplace(addr, &*it_and_ins.first);
+      symbol_tokens.emplace_back(SymAddrAndTokenPtr{addr, &*it_and_ins.first});
     });
   });
 
+  symbol_tokens.shrink_to_fit();
+
+  // For each symbol address, T entries are inserted into |symbol_tokens|, one
+  // for each token. These symbols are added in arbitrary address (as seen in
+  // /proc/kallsyms). Here we want to sort symbols by addresses, but at the same
+  // time preserve the order of tokens within each address.
+  // For instance, if kallsyms has: {0x41: connect_socket, 0x42: write_file}:
+  // Before sort: [(0x42, write), (0x42, file), (0x41, connect), (0x41, socket)]
+  // After sort: [(0x41, connect), (0x41, socket), (0x42, write), (0x42, file)]
+  std::stable_sort(symbol_tokens.begin(), symbol_tokens.end());
+
   // At this point we have broken down each symbol into a set of token hashes.
   // Now generate the token ids, putting high freq tokens first, so they use
   // only one byte to varint encode.
@@ -291,17 +316,17 @@
   tokens_.shrink_to_fit();
 
   buf_.resize(2 * 1024 * 1024);  // Based on real-word observations.
-  base_addr_ = symbols.empty() ? 0 : symbols.begin()->first;
+  base_addr_ = symbol_tokens.empty() ? 0 : symbol_tokens.begin()->addr;
   SymAddr prev_sym_addr = base_addr_;
   uint8_t* wptr = buf_.data();
 
-  for (auto it = symbols.begin(); it != symbols.end();) {
-    const SymAddr sym_addr = it->first;
+  for (auto it = symbol_tokens.begin(); it != symbol_tokens.end();) {
+    const SymAddr sym_addr = it->addr;
 
     // Find the iterator to the first token of the next symbol (or the end).
     auto sym_start = it;
     auto sym_end = it;
-    while (sym_end != symbols.end() && sym_end->first == sym_addr)
+    while (sym_end != symbol_tokens.end() && sym_end->addr == sym_addr)
       ++sym_end;
 
     // The range [sym_start, sym_end) has all the tokens for the current symbol.
@@ -322,8 +347,10 @@
     wptr = protozero::proto_utils::WriteVarInt(delta, wptr);
     // Append all the token ids.
     for (it = sym_start; it != sym_end;) {
-      PERFETTO_DCHECK(it->first == sym_addr);
-      TokenId token_id = it->second->second.id << 1;
+      PERFETTO_DCHECK(it->addr == sym_addr);
+      TokenMapPtr const token_map_entry = it->token_map_entry;
+      const TokenInfo& token_info = token_map_entry->second;
+      TokenId token_id = token_info.id << 1;
       ++it;
       token_id |= (it == sym_end) ? 1 : 0;  // Last one has LSB set to 1.
       wptr = protozero::proto_utils::WriteVarInt(token_id, wptr);
@@ -333,11 +360,19 @@
 
   buf_.resize(static_cast<size_t>(wptr - buf_.data()));
   buf_.shrink_to_fit();
+  base::MaybeReleaseAllocatorMemToOS();  // For Scudo, b/170217718.
 
-  PERFETTO_DLOG(
-      "Loaded %zu kalllsyms entries. Mem usage: %zu B (addresses) + %zu B "
-      "(tokens), total: %zu B",
-      num_syms_, addr_bytes(), tokens_.size_bytes(), size_bytes());
+  if (num_syms_ == 0) {
+    PERFETTO_ELOG(
+        "Failed to parse kallsyms. Kernel functions will not be symbolized. On "
+        "Linux this requires either running traced_probes as root or manually "
+        "lowering /proc/sys/kernel/kptr_restrict");
+  } else {
+    PERFETTO_DLOG(
+        "Loaded %zu kalllsyms entries. Mem usage: %zu B (addresses) + %zu B "
+        "(tokens), total: %zu B",
+        num_syms_, addr_bytes(), tokens_.size_bytes(), size_bytes());
+  }
 
   return num_syms_;
 }
@@ -362,6 +397,7 @@
   const uint8_t* const buf_end = &buf_[buf_.size()];
   bool parsing_addr = true;
   const uint8_t* next_rdptr = nullptr;
+  uint64_t sym_start_addr = 0;
   for (bool is_first_addr = true;; is_first_addr = false) {
     uint64_t v = 0;
     const auto* prev_rdptr = rdptr;
@@ -374,6 +410,7 @@
       if (addr > sym_rel_addr)
         break;
       next_rdptr = rdptr;
+      sym_start_addr = addr;
     } else {
       // This is a token. Wait for the EOF maker.
       parsing_addr = (v & 1) == 1;
@@ -383,6 +420,14 @@
   if (!next_rdptr)
     return "";
 
+  PERFETTO_DCHECK(sym_rel_addr >= sym_start_addr);
+
+  // If this address is too far from the start of the symbol, this is likely
+  // a pointer to something else (e.g. some vmalloc struct) and we just picked
+  // the very last symbol for a loader region.
+  if (sym_rel_addr - sym_start_addr > kSymMaxSizeBytes)
+    return "";
+
   // The address has been found. Now rejoin the tokens to form the symbol name.
 
   rdptr = next_rdptr;
diff --git a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h b/src/kallsyms/kernel_symbol_map.h
similarity index 97%
rename from src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h
rename to src/kallsyms/kernel_symbol_map.h
index c13546a..c69d86a 100644
--- a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h
+++ b/src/kallsyms/kernel_symbol_map.h
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACED_PROBES_FTRACE_KALLSYMS_KERNEL_SYMBOL_MAP_H_
-#define SRC_TRACED_PROBES_FTRACE_KALLSYMS_KERNEL_SYMBOL_MAP_H_
+#ifndef SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_
+#define SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_
 
 #include <stdint.h>
+
 #include <array>
 #include <forward_list>
+#include <string>
 #include <vector>
 
 namespace perfetto {
@@ -179,4 +181,4 @@
 
 }  // namespace perfetto
 
-#endif  // SRC_TRACED_PROBES_FTRACE_KALLSYMS_KERNEL_SYMBOL_MAP_H_
+#endif  // SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_
diff --git a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map_benchmark.cc b/src/kallsyms/kernel_symbol_map_benchmark.cc
similarity index 98%
rename from src/traced/probes/ftrace/kallsyms/kernel_symbol_map_benchmark.cc
rename to src/kallsyms/kernel_symbol_map_benchmark.cc
index e5b8cf7..7c3e9a9 100644
--- a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map_benchmark.cc
+++ b/src/kallsyms/kernel_symbol_map_benchmark.cc
@@ -21,7 +21,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
 #include "src/base/test/utils.h"
-#include "src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/kernel_symbol_map.h"
 
 namespace {
 
diff --git a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map_unittest.cc b/src/kallsyms/kernel_symbol_map_unittest.cc
similarity index 95%
rename from src/traced/probes/ftrace/kallsyms/kernel_symbol_map_unittest.cc
rename to src/kallsyms/kernel_symbol_map_unittest.cc
index 942820a..deba34a 100644
--- a/src/traced/probes/ftrace/kallsyms/kernel_symbol_map_unittest.cc
+++ b/src/kallsyms/kernel_symbol_map_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/traced/probes/ftrace/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/kernel_symbol_map.h"
 
 #include <inttypes.h>
 
@@ -122,7 +122,10 @@
   EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa19ULL), "one");
   EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa71ULL), "___se___v_e__n___");
   EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa91ULL), "NiNe");
-  EXPECT_EQ(kallsyms.Lookup(0xffffff8fffffffffULL), "NiNe");
+  EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa99ULL), "NiNe");
+
+  // This is too far from the last symbol and should fail.
+  EXPECT_EQ(kallsyms.Lookup(0xffffff8fffffffffULL), "");
 }
 
 TEST(KernelSymbolMapTest, GoldenTest) {
diff --git a/src/kallsyms/lazy_kernel_symbolizer.cc b/src/kallsyms/lazy_kernel_symbolizer.cc
new file mode 100644
index 0000000..8ed0af8
--- /dev/null
+++ b/src/kallsyms/lazy_kernel_symbolizer.cc
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/kallsyms/lazy_kernel_symbolizer.h"
+
+#include <string>
+
+#include <unistd.h>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/compiler.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/utils.h"
+#include "src/kallsyms/kernel_symbol_map.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/system_properties.h>
+#endif
+
+namespace perfetto {
+
+namespace {
+
+const char kKallsymsPath[] = "/proc/kallsyms";
+const char kPtrRestrictPath[] = "/proc/sys/kernel/kptr_restrict";
+const char kLowerPtrRestrictAndroidProp[] = "security.lower_kptr_restrict";
+
+// This class takes care of temporarily lowering kptr_restrict and putting it
+// back to the original value if necessary. It solves the following problem:
+// When reading /proc/kallsyms on Linux/Android, the symbol addresses can be
+// masked out (i.e. they are all 00000000) through the kptr_restrict file.
+// On Android kptr_restrict defaults to 2. On Linux, it depends on the
+// distribution. On Android we cannot simply write() kptr_restrict ourselves.
+// Doing so requires the union of:
+// - filesystem ACLs: kptr_restrict is rw-r--r--// and owned by root.
+// - Selinux rules: kptr_restrict is labelled as proc_security and restricted.
+// - CAP_SYS_ADMIN: when writing to kptr_restrict, the kernel enforces that the
+//                  caller has the SYS_ADMIN capability at write() time.
+// The latter would be problematic, we don't want traced_probes to have that,
+// CAP_SYS_ADMIN is too broad.
+// Instead, we opt for the following model: traced_probes sets an Android
+// property introduced in S (security.lower_kptr_restrict); init (which
+// satisfies all the requirements above) in turn sets kptr_restrict.
+// On Linux and standalone builds, instead, we don't have many options. Either:
+// - The system administrator takes care of lowering kptr_restrict before
+//   tracing.
+// - The system administrator runs traced_probes as root / CAP_SYS_ADMIN and we
+//   temporarily lower and restore kptr_restrict ourselves.
+// This class deals with all these cases.
+class ScopedKptrUnrestrict {
+ public:
+  ScopedKptrUnrestrict();   // Lowers kptr_restrict if necessary.
+  ~ScopedKptrUnrestrict();  // Restores the initial kptr_restrict.
+
+ private:
+  static void WriteKptrRestrict(const std::string&);
+
+  static const bool kUseAndroidProperty;
+  std::string initial_value_;
+  bool restore_on_dtor_ = true;
+};
+
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+// This is true only on Android in-tree builds (not on standalone).
+const bool ScopedKptrUnrestrict::kUseAndroidProperty = true;
+#else
+const bool ScopedKptrUnrestrict::kUseAndroidProperty = false;
+#endif
+
+ScopedKptrUnrestrict::ScopedKptrUnrestrict() {
+  if (LazyKernelSymbolizer::CanReadKernelSymbolAddresses()) {
+    // Everything seems to work (e.g., we are running as root and kptr_restrict
+    // is < 2). Don't touching anything.
+    restore_on_dtor_ = false;
+    return;
+  }
+
+  if (kUseAndroidProperty) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+    __system_property_set(kLowerPtrRestrictAndroidProp, "1");
+#endif
+    // Init takes some time to react to the property change.
+    // Unfortunately, we cannot read kptr_restrict because of SELinux. Instead,
+    // we detect this by reading the initial lines of kallsyms and checking
+    // that they are non-zero. This loop waits for at most 250ms (50 * 5ms).
+    for (int attempt = 1; attempt <= 50; ++attempt) {
+      usleep(5000);
+      if (LazyKernelSymbolizer::CanReadKernelSymbolAddresses())
+        return;
+    }
+    PERFETTO_ELOG("kallsyms addresses are still masked after setting %s",
+                  kLowerPtrRestrictAndroidProp);
+    return;
+  }  // if (kUseAndroidProperty)
+
+  // On Linux and Android standalone, read the kptr_restrict value and lower it
+  // if needed.
+  bool read_res = base::ReadFile(kPtrRestrictPath, &initial_value_);
+  if (!read_res) {
+    PERFETTO_PLOG("Failed to read %s", kPtrRestrictPath);
+    return;
+  }
+
+  // Progressively lower kptr_restrict until we can read kallsyms.
+  for (int value = atoi(initial_value_.c_str()); value > 0; --value) {
+    WriteKptrRestrict(std::to_string(value));
+    if (LazyKernelSymbolizer::CanReadKernelSymbolAddresses())
+      return;
+  }
+}
+
+ScopedKptrUnrestrict::~ScopedKptrUnrestrict() {
+  if (!restore_on_dtor_)
+    return;
+  if (kUseAndroidProperty) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+    __system_property_set(kLowerPtrRestrictAndroidProp, "0");
+#endif
+  } else if (!initial_value_.empty()) {
+    WriteKptrRestrict(initial_value_);
+  }
+}
+
+void ScopedKptrUnrestrict::WriteKptrRestrict(const std::string& value) {
+  // Note: kptr_restrict requires O_WRONLY. O_RDWR won't work.
+  PERFETTO_DCHECK(!value.empty());
+  base::ScopedFile fd = base::OpenFile(kPtrRestrictPath, O_WRONLY);
+  auto wsize = write(*fd, value.c_str(), value.size());
+  if (wsize <= 0)
+    PERFETTO_PLOG("Failed to set %s to %s", kPtrRestrictPath, value.c_str());
+}
+
+}  // namespace
+
+LazyKernelSymbolizer::LazyKernelSymbolizer() = default;
+LazyKernelSymbolizer::~LazyKernelSymbolizer() = default;
+
+KernelSymbolMap* LazyKernelSymbolizer::GetOrCreateKernelSymbolMap() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  if (symbol_map_)
+    return symbol_map_.get();
+
+  symbol_map_.reset(new KernelSymbolMap());
+
+  // If kptr_restrict is set, try temporarily lifting it (it works only if
+  // traced_probes is run as a privileged user).
+  ScopedKptrUnrestrict kptr_unrestrict;
+  symbol_map_->Parse(kKallsymsPath);
+  return symbol_map_.get();
+}
+
+void LazyKernelSymbolizer::Destroy() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  symbol_map_.reset();
+  base::MaybeReleaseAllocatorMemToOS();  // For Scudo, b/170217718.
+}
+
+// static
+bool LazyKernelSymbolizer::CanReadKernelSymbolAddresses(
+    const char* ksyms_path_for_testing) {
+  auto* path = ksyms_path_for_testing ? ksyms_path_for_testing : kKallsymsPath;
+  base::ScopedFile fd = base::OpenFile(path, O_RDONLY);
+  if (!fd) {
+    PERFETTO_PLOG("open(%s) failed", kKallsymsPath);
+    return false;
+  }
+  // Don't just use fscanf() as that might read the whole file (b/36473442).
+  char buf[4096];
+  auto rsize_signed = base::Read(*fd, buf, sizeof(buf) - 1);
+  if (rsize_signed <= 0) {
+    PERFETTO_PLOG("read(%s) failed", kKallsymsPath);
+    return false;
+  }
+  size_t rsize = static_cast<size_t>(rsize_signed);
+  buf[rsize] = '\0';
+
+  // Iterate over the first page of kallsyms. If we find any non-zero address
+  // call it success. If all addresses are 0, pessimistically assume
+  // kptr_restrict is still restricted.
+  // We cannot look only at the first line because on some devices
+  // /proc/kallsyms can look like this (note the zeros in the first two addrs):
+  // 0000000000000000 A fixed_percpu_data
+  // 0000000000000000 A __per_cpu_start
+  // 0000000000001000 A cpu_debug_store
+  bool reading_addr = true;
+  bool addr_is_zero = true;
+  for (size_t i = 0; i < rsize; i++) {
+    const char c = buf[i];
+    if (reading_addr) {
+      const bool is_hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
+      if (is_hex) {
+        addr_is_zero = addr_is_zero && c == '0';
+      } else {
+        if (!addr_is_zero)
+          return true;
+        reading_addr = false;  // Will consume the rest of the line until \n.
+      }
+    } else if (c == '\n') {
+      reading_addr = true;
+    }  // if (!reading_addr)
+  }    // for char in buf
+
+  return false;
+}
+
+}  // namespace perfetto
diff --git a/src/kallsyms/lazy_kernel_symbolizer.h b/src/kallsyms/lazy_kernel_symbolizer.h
new file mode 100644
index 0000000..6f954e5
--- /dev/null
+++ b/src/kallsyms/lazy_kernel_symbolizer.h
@@ -0,0 +1,66 @@
+/*
+ * 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_KALLSYMS_LAZY_KERNEL_SYMBOLIZER_H_
+#define SRC_KALLSYMS_LAZY_KERNEL_SYMBOLIZER_H_
+
+#include <memory>
+
+#include "perfetto/ext/base/thread_checker.h"
+
+namespace perfetto {
+
+class KernelSymbolMap;
+
+// This class is a wrapper around KernelSymbolMap. It serves two purposes:
+// 1. Deals with /proc/kallsyms reads and temporary lowering of kptr_restrict.
+//    KernelSymbolMap is just a parser and doesn't do I/O.
+// 2. Allows to share the same KernelSymbolMap instance across several clients
+//    and tear it down when tracing stops.
+//
+// LazyKernelSymbolizer is owned by the (one) FtraceController. FtraceController
+// handles LazyKernelSymbolizer pointers to  N CpuReader-s (one per CPU). In
+// this way all CpuReader instances can share the same symbol map instance.
+// The object being shared is LazyKernelSymbolizer, which is cheap and always
+// valid. LazyKernelSymbolizer may or may not contain a valid symbol map.
+class LazyKernelSymbolizer {
+ public:
+  // Constructs an empty instance. Does NOT load any symbols upon construction.
+  // Loading and parsing happens on the first GetOrCreateKernelSymbolMap() call.
+  LazyKernelSymbolizer();
+  ~LazyKernelSymbolizer();
+
+  // Returns |instance_|, creating it if doesn't exist or was destroyed.
+  KernelSymbolMap* GetOrCreateKernelSymbolMap();
+
+  bool is_valid() const { return !!symbol_map_; }
+
+  // Destroys the |symbol_map_| freeing up memory. A further call to
+  // GetOrCreateKernelSymbolMap() will create it again.
+  void Destroy();
+
+  // Exposed for testing.
+  static bool CanReadKernelSymbolAddresses(
+      const char* ksyms_path_for_testing = nullptr);
+
+ private:
+  std::unique_ptr<KernelSymbolMap> symbol_map_;
+  PERFETTO_THREAD_CHECKER(thread_checker_)
+};
+
+}  // namespace perfetto
+
+#endif  // SRC_KALLSYMS_LAZY_KERNEL_SYMBOLIZER_H_
diff --git a/src/kallsyms/lazy_kernel_symbolizer_unittest.cc b/src/kallsyms/lazy_kernel_symbolizer_unittest.cc
new file mode 100644
index 0000000..c219dda
--- /dev/null
+++ b/src/kallsyms/lazy_kernel_symbolizer_unittest.cc
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/kallsyms/lazy_kernel_symbolizer.h"
+
+#include <inttypes.h>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace {
+
+const char kUnrestrictedKallsyms[] = R"(
+0000000000000000 A fixed_percpu_data
+0000000000000000 A __per_cpu_start
+0000000000001000 A cpu_debug_store
+0000000000002000 A irq_stack_backing_store
+0000000000006000 A cpu_tss_rw
+0000000000009000 A gdt_page
+000000000000a000 A entry_stack_storage
+000000000000b000 A exception_stacks
+0000000000010000 A espfix_stack
+0000000000010008 A espfix_waddr
+0000000000010010 A cpu_llc_id
+0000000000010020 A mce_banks_array
+0000000000010220 A mce_num_banks
+0000000000010228 A cpu_sibling_map
+0000000000010230 A cpu_core_map
+0000000000010238 A cpu_die_map
+0000000000010240 A cpu_info
+0000000000010330 A cpu_llc_shared_map
+0000000000010338 A cpu_number
+0000000000010340 A this_cpu_off
+0000000000010348 A x86_cpu_to_apicid
+000000000001034a A x86_bios_cpu_apicid
+000000000001034c A x86_cpu_to_acpiid
+0000000000010350 A sched_core_priority
+0000000000011000 A svm_data
+0000000000011008 A current_tsc_ratio
+0000000000011010 A saved_epb
+0000000000011018 A cluster_masks
+0000000000011020 A x86_cpu_to_logical_apicid
+0000000000011028 A ipi_mask
+0000000000011030 A menu_devices
+0000000000011098 A cpu_loops_per_jiffy
+00000000000110a0 A cpu_hw_events
+00000000000123b0 A pmc_prev_left
+00000000000125b0 A perf_nmi_tstamp
+0000000000013000 A bts_ctx
+0000000000016000 A insn_buffer
+0000000000016008 A pt_ctx
+00000000000160b0 A cpu_tsc_khz
+00000000000160b8 A current_vcpu
+00000000000160c0 A loaded_vmcss_on_cpu
+00000000000160d0 A current_vmcs
+00000000000160d8 A vmxarea
+00000000000160e0 A blocked_vcpu_on_cpu
+00000000000160f0 A blocked_vcpu_on_cpu_lock
+00000000000160f8 A irq_regs
+0000000000016100 A nmi_state
+0000000000016108 A nmi_cr2
+0000000000016110 A update_debug_stack
+0000000000016118 A last_nmi_rip
+0000000000016120 A nmi_stats
+0000000000016130 A swallow_nmi
+0000000000016140 A vector_irq
+0000000000028458 A processor_device_array
+0000000000028460 A acpi_cpuidle_device
+0000000000028470 A acpi_cstate
+00000000000284c0 A cpufreq_thermal_reduction_pctg
+00000000000284c8 A cpc_desc_ptr
+00000000000284d0 A cpu_pcc_subspace_idx
+00000000000284d8 A irq_randomness
+00000000000284f8 A batched_entropy_u64
+0000000000028540 A batched_entropy_u32
+00000000000285c0 A drm_unplug_srcu_srcu_data
+0000000000028740 A device_links_srcu_srcu_data
+00000000000288c0 A cpu_sys_devices
+00000000000288c8 A ci_cpu_cacheinfo
+00000000000288e0 A ci_cache_dev
+00000000000288e8 A ci_index_dev
+0000000000028900 A wakeup_srcu_srcu_data
+0000000000028a80 A flush_idx
+0000000000028ac0 A dax_srcu_srcu_data
+0000000000028c40 A cpufreq_cpu_data
+0000000000028c80 A cpufreq_transition_notifier_list_head_srcu_data
+0000000000028e00 A cpu_dbs
+0000000000028e30 A cpuidle_devices
+0000000000028e38 A cpuidle_dev
+00000000000290f8 A netdev_alloc_cache
+0000000000029110 A napi_alloc_cache
+0000000000029330 A flush_works
+0000000000029360 A bpf_redirect_info
+0000000000029388 A bpf_sp
+0000000000029588 A nf_skb_duplicated
+000000000002958c A xt_recseq
+0000000000029590 A rt_cache_stat
+00000000000295b0 A tsq_tasklet
+00000000000295e8 A xfrm_trans_tasklet
+0000000000029628 A radix_tree_preloads
+0000000000029640 A irq_stat
+00000000000296c0 A cyc2ns
+0000000000029700 A cpu_tlbstate
+0000000000029780 A flush_tlb_info
+00000000000297c0 A cpu_worker_pools
+0000000000029ec0 A runqueues
+000000000002aa80 A sched_clock_data
+000000000002aac0 A osq_node
+000000000002ab00 A qnodes
+000000000002ab40 A rcu_data
+000000000002ae80 A cfd_data
+000000000002aec0 A call_single_queue
+000000000002af00 A csd_data
+000000000002af40 A softnet_data
+000000000002b200 A rt_uncached_list
+000000000002b240 A rt6_uncached_list
+000000000002b258 A __per_cpu_end
+ffffffffb7e00000 T startup_64
+ffffffffb7e00000 T _stext
+ffffffffb7e00000 T _text
+ffffffffb7e00030 T secondary_startup_64
+ffffffffb7e000e0 T verify_cpu
+ffffffffb7e001e0 T start_cpu0
+ffffffffb7e001f0 T __startup_64
+)";
+
+const char kRestrictedKallsyms[] = R"(
+0000000000000000 A fixed_percpu_data
+0000000000000000 A __per_cpu_start
+0000000000000000 A cpu_debug_store
+0000000000000000 A irq_stack_backing_store
+0000000000000000 A cpu_tss_rw
+0000000000000000 A gdt_page
+0000000000000000 A entry_stack_storage
+0000000000000000 A exception_stacks
+0000000000000000 A espfix_stack
+0000000000000000 A espfix_waddr
+0000000000000000 A cpu_llc_id
+0000000000000000 A mce_banks_array
+0000000000000000 A mce_num_banks
+0000000000000000 A cpu_sibling_map
+0000000000000000 A cpu_core_map
+0000000000000000 A cpu_die_map
+0000000000000000 A cpu_info
+0000000000000000 A cpu_llc_shared_map
+0000000000000000 A cpu_number
+0000000000000000 A this_cpu_off
+0000000000000000 A x86_cpu_to_apicid
+0000000000000000 A x86_bios_cpu_apicid
+0000000000000000 A x86_cpu_to_acpiid
+0000000000000000 A sched_core_priority
+0000000000000000 A svm_data
+0000000000000000 A current_tsc_ratio
+0000000000000000 A saved_epb
+0000000000000000 A cluster_masks
+0000000000000000 A x86_cpu_to_logical_apicid
+0000000000000000 A ipi_mask
+0000000000000000 A menu_devices
+0000000000000000 A cpu_loops_per_jiffy
+0000000000000000 A cpu_hw_events
+0000000000000000 A pmc_prev_left
+0000000000000000 A perf_nmi_tstamp
+0000000000000000 A bts_ctx
+0000000000000000 A insn_buffer
+0000000000000000 A pt_ctx
+0000000000000000 A cpu_tsc_khz
+0000000000000000 A current_vcpu
+0000000000000000 A loaded_vmcss_on_cpu
+0000000000000000 A current_vmcs
+0000000000000000 A vmxarea
+0000000000000000 A blocked_vcpu_on_cpu
+0000000000000000 A blocked_vcpu_on_cpu_lock
+0000000000000000 A irq_regs
+0000000000000000 A nmi_state
+0000000000000000 A nmi_cr2
+0000000000000000 A update_debug_stack
+0000000000000000 A last_nmi_rip
+0000000000000000 A nmi_stats
+0000000000000000 A swallow_nmi
+0000000000000000 A vector_irq
+0000000000000000 A processor_device_array
+0000000000000000 A acpi_cpuidle_device
+0000000000000000 A acpi_cstate
+0000000000000000 A cpufreq_thermal_reduction_pctg
+0000000000000000 A cpc_desc_ptr
+0000000000000000 A cpu_pcc_subspace_idx
+0000000000000000 A irq_randomness
+0000000000000000 A batched_entropy_u64
+0000000000000000 A batched_entropy_u32
+0000000000000000 A drm_unplug_srcu_srcu_data
+0000000000000000 A device_links_srcu_srcu_data
+0000000000000000 A cpu_sys_devices
+0000000000000000 A ci_cpu_cacheinfo
+0000000000000000 A ci_cache_dev
+0000000000000000 A ci_index_dev
+0000000000000000 A wakeup_srcu_srcu_data
+0000000000000000 A flush_idx
+0000000000000000 A dax_srcu_srcu_data
+0000000000000000 A cpufreq_cpu_data
+0000000000000000 A cpufreq_transition_notifier_list_head_srcu_data
+0000000000000000 A cpu_dbs
+0000000000000000 A cpuidle_devices
+0000000000000000 A cpuidle_dev
+0000000000000000 A netdev_alloc_cache
+0000000000000000 A napi_alloc_cache
+0000000000000000 A flush_works
+0000000000000000 A bpf_redirect_info
+0000000000000000 A bpf_sp
+0000000000000000 A nf_skb_duplicated
+0000000000000000 A xt_recseq
+0000000000000000 A rt_cache_stat
+0000000000000000 A tsq_tasklet
+0000000000000000 A xfrm_trans_tasklet
+0000000000000000 A radix_tree_preloads
+0000000000000000 A irq_stat
+0000000000000000 A cyc2ns
+0000000000000000 A cpu_tlbstate
+0000000000000000 A flush_tlb_info
+0000000000000000 A cpu_worker_pools
+0000000000000000 A runqueues
+0000000000000000 A sched_clock_data
+0000000000000000 A osq_node
+0000000000000000 A qnodes
+0000000000000000 A rcu_data
+0000000000000000 A cfd_data
+0000000000000000 A call_single_queue
+0000000000000000 A csd_data
+0000000000000000 A softnet_data
+0000000000000000 A rt_uncached_list
+0000000000000000 A rt6_uncached_list
+0000000000000000 A __per_cpu_end
+0000000000000000 T startup_64
+0000000000000000 T _stext
+0000000000000000 T _text
+0000000000000000 T secondary_startup_64
+0000000000000000 T verify_cpu
+0000000000000000 T start_cpu0
+0000000000000000 T __startup_64
+)";
+
+TEST(LazyKernelSymbolizerTest, CanReadKernelSymbolAddresses) {
+  {
+    base::TempFile tmp = base::TempFile::Create();
+    base::WriteAll(tmp.fd(), kRestrictedKallsyms, sizeof(kRestrictedKallsyms));
+    base::FlushFile(tmp.fd());
+    EXPECT_FALSE(
+        LazyKernelSymbolizer::CanReadKernelSymbolAddresses(tmp.path().c_str()));
+  }
+
+  {
+    base::TempFile tmp = base::TempFile::Create();
+    base::WriteAll(tmp.fd(), kUnrestrictedKallsyms,
+                   sizeof(kUnrestrictedKallsyms));
+    base::FlushFile(tmp.fd());
+    EXPECT_TRUE(
+        LazyKernelSymbolizer::CanReadKernelSymbolAddresses(tmp.path().c_str()));
+  }
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index 4b06501..ca60ae7 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -40,10 +40,6 @@
   sources = [ "trigger_perfetto_main.cc" ]
 }
 
-source_set("perfetto_atoms") {
-  sources = [ "perfetto_atoms.h" ]
-}
-
 # Contains all the implementation but not the main() entry point. This target
 # is shared both by the executable and tests.
 source_set("perfetto_cmd") {
@@ -52,13 +48,12 @@
     "../../include/perfetto/ext/traced",
   ]
   deps = [
-    ":perfetto_atoms",
     ":trigger_producer",
     "../../gn:default_deps",
     "../../protos/perfetto/common:cpp",
     "../../protos/perfetto/config:cpp",
     "../../protos/perfetto/config/ftrace:cpp",
-    "../android_internal:lazy_library_loader",
+    "../android_stats",
     "../base",
     "../protozero",
     "../tracing/ipc/consumer",
@@ -80,6 +75,7 @@
     "rate_limiter.h",
   ]
   if (is_android) {
+    deps += [ "../android_internal:lazy_library_loader" ]
     sources += [ "perfetto_cmd_android.cc" ]
   }
 }
@@ -92,6 +88,7 @@
   deps = [
     ":trigger_producer",
     "../../gn:default_deps",
+    "../android_stats",
     "../base",
     "../tracing/ipc/producer",
   ]
diff --git a/src/perfetto_cmd/config.cc b/src/perfetto_cmd/config.cc
index 1f1d2fd..e51dc80 100644
--- a/src/perfetto_cmd/config.cc
+++ b/src/perfetto_cmd/config.cc
@@ -49,6 +49,11 @@
     return true;
   }
 
+  if (arg == "0") {
+    *out = 0;
+    return true;
+  }
+
   ValueUnit value_unit{};
   if (!SplitValueAndUnit(arg, &value_unit))
     return false;
diff --git a/src/perfetto_cmd/config_unittest.cc b/src/perfetto_cmd/config_unittest.cc
index 99b57f9..cc1db34 100644
--- a/src/perfetto_cmd/config_unittest.cc
+++ b/src/perfetto_cmd/config_unittest.cc
@@ -45,6 +45,12 @@
   EXPECT_EQ(config.duration_ms(), 2u);
 }
 
+TEST_F(CreateConfigFromOptionsTest, ZeroTime) {
+  options.time = "0";
+  ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+  EXPECT_EQ(config.duration_ms(), 0u);
+}
+
 TEST_F(CreateConfigFromOptionsTest, Seconds) {
   options.time = "100s";
   ASSERT_TRUE(CreateConfigFromOptions(options, &config));
@@ -63,6 +69,12 @@
   EXPECT_EQ(config.duration_ms(), 2 * 60 * 60 * 1000u);
 }
 
+TEST_F(CreateConfigFromOptionsTest, ZeroBufferSize) {
+  options.buffer_size = "0";
+  ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+  EXPECT_EQ(config.buffers()[0].size_kb(), 0u);
+}
+
 TEST_F(CreateConfigFromOptionsTest, Kilobyte) {
   options.buffer_size = "2kb";
   ASSERT_TRUE(CreateConfigFromOptions(options, &config));
diff --git a/src/perfetto_cmd/packet_writer.cc b/src/perfetto_cmd/packet_writer.cc
index 4897469..32b0b03 100644
--- a/src/perfetto_cmd/packet_writer.cc
+++ b/src/perfetto_cmd/packet_writer.cc
@@ -38,9 +38,7 @@
 namespace perfetto {
 namespace {
 
-using protozero::proto_utils::kMessageLengthFieldSize;
 using protozero::proto_utils::MakeTagLengthDelimited;
-using protozero::proto_utils::WriteRedundantVarInt;
 using protozero::proto_utils::WriteVarInt;
 using Preamble = std::array<char, 16>;
 
diff --git a/src/perfetto_cmd/perfetto_atoms.h b/src/perfetto_cmd/perfetto_atoms.h
deleted file mode 100644
index 35903ea..0000000
--- a/src/perfetto_cmd/perfetto_atoms.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_PERFETTO_CMD_PERFETTO_ATOMS_H_
-#define SRC_PERFETTO_CMD_PERFETTO_ATOMS_H_
-
-namespace perfetto {
-
-// This must match the values of the PerfettoUploadEvent enum in:
-// frameworks/base/cmds/statsd/src/atoms.proto
-enum class PerfettoStatsdAtom {
-  kUndefined = 0,
-
-  kTraceBegin = 1,
-  kBackgroundTraceBegin = 2,
-
-  kOnConnect = 3,
-  kOnTracingDisabled = 4,
-
-  kUploadDropboxBegin = 5,
-  kUploadDropboxSuccess = 6,
-  kUploadDropboxFailure = 7,
-
-  kUploadIncidentBegin = 8,
-  kUploadIncidentSuccess = 9,
-  kUploadIncidentFailure = 10,
-
-  kFinalizeTraceAndExit = 11,
-
-  kTriggerBegin = 12,
-  kTriggerSuccess = 13,
-  kTriggerFailure = 14,
-
-  kHitGuardrails = 15,
-  kOnTimeout = 16,
-  kNotUploadingEmptyTrace = 17,
-};
-
-}  // namespace perfetto
-
-#endif  // SRC_PERFETTO_CMD_PERFETTO_ATOMS_H_
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 1925563..8b8ea2d 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -41,6 +41,7 @@
 #include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/uuid.h"
+#include "perfetto/ext/base/version.h"
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
@@ -49,6 +50,7 @@
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "perfetto/tracing/core/tracing_service_state.h"
+#include "src/android_stats/statsd_logging_helper.h"
 #include "src/perfetto_cmd/config.h"
 #include "src/perfetto_cmd/packet_writer.h"
 #include "src/perfetto_cmd/pbtxt_to_pb.h"
@@ -134,13 +136,27 @@
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
 }
 
+base::Optional<PerfettoStatsdAtom> ConvertRateLimiterResponseToAtom(
+    RateLimiter::ShouldTraceResponse resp) {
+  switch (resp) {
+    case RateLimiter::kNotAllowedOnUserBuild:
+      return PerfettoStatsdAtom::kCmdUserBuildTracingNotAllowed;
+    case RateLimiter::kFailedToInitState:
+      return PerfettoStatsdAtom::kCmdFailedToInitGuardrailState;
+    case RateLimiter::kInvalidState:
+      return PerfettoStatsdAtom::kCmdInvalidGuardrailState;
+    case RateLimiter::kHitUploadLimit:
+      return PerfettoStatsdAtom::kCmdHitUploadLimit;
+    case RateLimiter::kOkToTrace:
+      return base::nullopt;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 }  // namespace
 
 const char* kStateDir = "/data/misc/perfetto-traces";
 
-using protozero::proto_utils::MakeTagLengthDelimited;
-using protozero::proto_utils::WriteVarInt;
-
 int PerfettoCmd::PrintUsage(const char* argv0) {
   PERFETTO_ELOG(R"(
 Usage: %s
@@ -161,6 +177,8 @@
                              human-readable text.
   --query-raw              : Like --query, but prints raw proto-encoded bytes
                              of tracing_service_state.proto.
+  --save-for-bugreport     : If a trace with bugreport_score > 0 is running, it
+                             saves it into a file. Outputs the path when done.
   --help           -h
 
 
@@ -170,8 +188,6 @@
   --size           -s      : Max file size N[mb,gb] (default: in-memory ring-buffer only)
   ATRACE_CAT               : Record ATRACE_CAT (e.g. wm)
   FTRACE_GROUP/FTRACE_NAME : Record ftrace event (e.g. sched/sched_switch)
-  FTRACE_GROUP/*           : Record all events in group (e.g. sched/*)
-
 
 statsd-specific flags:
   --alert-id           : ID of the alert that triggered this trace.
@@ -193,6 +209,7 @@
 
   enum LongOption {
     OPT_ALERT_ID = 1000,
+    OPT_BUGREPORT,
     OPT_CONFIG_ID,
     OPT_CONFIG_UID,
     OPT_SUBSCRIPTION_ID,
@@ -208,8 +225,9 @@
     OPT_STOP,
     OPT_QUERY,
     OPT_QUERY_RAW,
+    OPT_VERSION,
   };
-  static const struct option long_options[] = {
+  static const option long_options[] = {
       {"help", no_argument, nullptr, 'h'},
       {"config", required_argument, nullptr, 'c'},
       {"out", required_argument, nullptr, 'o'},
@@ -233,6 +251,8 @@
       {"app", required_argument, nullptr, OPT_ATRACE_APP},
       {"query", no_argument, nullptr, OPT_QUERY},
       {"query-raw", no_argument, nullptr, OPT_QUERY_RAW},
+      {"version", no_argument, nullptr, OPT_VERSION},
+      {"save-for-bugreport", no_argument, nullptr, OPT_BUGREPORT},
       {nullptr, 0, nullptr, 0}};
 
   int option_index = 0;
@@ -307,7 +327,7 @@
 
     if (option == OPT_UPLOAD) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-      dropbox_tag_ = "perfetto";
+      is_uploading_ = true;
       continue;
 #else
       PERFETTO_ELOG("--upload is only supported on Android");
@@ -318,7 +338,7 @@
     if (option == OPT_DROPBOX) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
       PERFETTO_CHECK(optarg);
-      dropbox_tag_ = optarg;
+      is_uploading_ = true;
       continue;
 #else
       PERFETTO_ELOG("--dropbox is only supported on Android");
@@ -403,6 +423,16 @@
       continue;
     }
 
+    if (option == OPT_VERSION) {
+      printf("%s\n", base::GetVersionString());
+      return 0;
+    }
+
+    if (option == OPT_BUGREPORT) {
+      bugreport_ = true;
+      continue;
+    }
+
     return PrintUsage(argv[0]);
   }
 
@@ -431,6 +461,12 @@
     return 1;
   }
 
+  if (bugreport_ &&
+      (is_attach() | is_detach() || query_service_ || has_config_options)) {
+    PERFETTO_ELOG("--save-for-bugreport cannot take any other argument");
+    return 1;
+  }
+
   // Parse the trace config. It can be either:
   // 1) A proto-encoded file/stdin (-c ...).
   // 2) A proto-text file/stdin (-c ... --txt).
@@ -441,7 +477,7 @@
 
   std::vector<std::string> triggers_to_activate;
   bool parsed = false;
-  const bool will_trace = !is_attach() && !query_service_;
+  const bool will_trace = !is_attach() && !query_service_ && !bugreport_;
   if (!will_trace) {
     if ((!trace_config_raw.empty() || has_config_options)) {
       PERFETTO_ELOG("Cannot specify a trace config with this option");
@@ -493,18 +529,24 @@
     uuid_ = uuid.ToString();
   }
 
-  if (!trace_config_->incident_report_config().destination_package().empty()) {
-    if (dropbox_tag_.empty()) {
-      PERFETTO_ELOG(
-          "Unexpected IncidentReportConfig without --dropbox / --upload.");
-      return 1;
-    }
+  if (!trace_config_->incident_report_config().destination_package().empty() &&
+      !is_uploading_) {
+    PERFETTO_ELOG(
+        "Unexpected IncidentReportConfig without --dropbox / --upload.");
+    return 1;
+  }
+
+  if (trace_config_->activate_triggers().empty() &&
+      trace_config_->incident_report_config().destination_package().empty() &&
+      is_uploading_) {
+    PERFETTO_ELOG("Missing IncidentReportConfig with --dropbox / --upload.");
+    return 1;
   }
 
   // Set up the output file. Either --out or --dropbox are expected, with the
   // only exception of --attach. In this case the output file is passed when
   // detaching.
-  if (!trace_out_path_.empty() && !dropbox_tag_.empty()) {
+  if (!trace_out_path_.empty() && is_uploading_) {
     PERFETTO_ELOG(
         "Can't log to a file (--out) and DropBox (--dropbox) at the same "
         "time");
@@ -512,7 +554,7 @@
   }
 
   if (!trace_config_->output_path().empty()) {
-    if (!trace_out_path_.empty() || !dropbox_tag_.empty()) {
+    if (!trace_out_path_.empty() || is_uploading_) {
       PERFETTO_ELOG(
           "Can't pass --out or --dropbox if output_path is set in the "
           "trace config");
@@ -542,7 +584,7 @@
   bool open_out_file = true;
   if (!will_trace) {
     open_out_file = false;
-    if (!trace_out_path_.empty() || !dropbox_tag_.empty()) {
+    if (!trace_out_path_.empty() || is_uploading_) {
       PERFETTO_ELOG("Can't pass an --out file (or --dropbox) with this option");
       return 1;
     }
@@ -550,7 +592,7 @@
              (trace_config_->write_into_file() &&
               !trace_config_->output_path().empty())) {
     open_out_file = false;
-  } else if (trace_out_path_.empty() && dropbox_tag_.empty()) {
+  } else if (trace_out_path_.empty() && !is_uploading_) {
     PERFETTO_ELOG("Either --out or --dropbox is required");
     return 1;
   } else if (is_detach() && !trace_config_->write_into_file()) {
@@ -597,6 +639,8 @@
   // the options.
   if (!triggers_to_activate.empty()) {
     LogUploadEvent(PerfettoStatsdAtom::kTriggerBegin);
+    LogTriggerEvents(PerfettoTriggerAtom::kCmdTrigger, triggers_to_activate);
+
     bool finished_with_success = false;
     TriggerProducer producer(
         &task_runner_,
@@ -610,11 +654,13 @@
       LogUploadEvent(PerfettoStatsdAtom::kTriggerSuccess);
     } else {
       LogUploadEvent(PerfettoStatsdAtom::kTriggerFailure);
+      LogTriggerEvents(PerfettoTriggerAtom::kCmdTriggerFail,
+                       triggers_to_activate);
     }
     return finished_with_success ? 0 : 1;
   }
 
-  if (query_service_) {
+  if (query_service_ || bugreport_) {
     consumer_endpoint_ =
         ConsumerIPCClient::Connect(GetConsumerSocket(), this, &task_runner_);
     task_runner_.Run();
@@ -636,7 +682,7 @@
 
   RateLimiter::Args args{};
   args.is_user_build = IsUserBuild();
-  args.is_dropbox = !dropbox_tag_.empty();
+  args.is_uploading = is_uploading_;
   args.current_time = base::GetWallTimeS();
   args.ignore_guardrails = ignore_guardrails;
   args.allow_user_build_tracing = trace_config_->allow_user_build_tracing();
@@ -650,7 +696,7 @@
   if (!args.unique_session_name.empty())
     base::MaybeSetThreadName("p-" + args.unique_session_name);
 
-  if (args.is_dropbox && !args.ignore_guardrails &&
+  if (args.is_uploading && !args.ignore_guardrails &&
       (trace_config_->duration_ms() == 0 &&
        trace_config_->trigger_config().trigger_timeout_ms() == 0)) {
     PERFETTO_ELOG("Can't trace indefinitely when tracing to Dropbox.");
@@ -673,8 +719,11 @@
     LogUploadEvent(PerfettoStatsdAtom::kBackgroundTraceBegin);
   }
 
-  if (!limiter.ShouldTrace(args)) {
+  auto err_atom = ConvertRateLimiterResponseToAtom(limiter.ShouldTrace(args));
+  if (err_atom) {
+    // TODO(lalitm): remove this once we're ready on server side.
     LogUploadEvent(PerfettoStatsdAtom::kHitGuardrails);
+    LogUploadEvent(err_atom.value());
     return 1;
   }
 
@@ -699,6 +748,19 @@
     return;
   }
 
+  if (bugreport_) {
+    consumer_endpoint_->SaveTraceForBugreport(
+        [](bool success, const std::string& msg) {
+          if (success) {
+            PERFETTO_ILOG("Trace saved into %s", msg.c_str());
+            exit(0);
+          }
+          PERFETTO_ELOG("%s", msg.c_str());
+          exit(1);
+        });
+    return;
+  }
+
   if (is_attach()) {
     consumer_endpoint_->Attach(attach_key_);
     return;
@@ -712,7 +774,7 @@
   }
 
   PERFETTO_DCHECK(trace_config_);
-  trace_config_->set_enable_extra_guardrails(!dropbox_tag_.empty());
+  trace_config_->set_enable_extra_guardrails(is_uploading_);
 
   base::ScopedFile optional_fd;
   if (trace_config_->write_into_file() && trace_config_->output_path().empty())
@@ -728,7 +790,8 @@
   // Failsafe mechanism to avoid waiting indefinitely if the service hangs.
   if (expected_duration_ms_) {
     uint32_t trace_timeout =
-        expected_duration_ms_ + 60000 + trace_config_->flush_timeout_ms();
+        expected_duration_ms_ + 60000 + trace_config_->flush_timeout_ms() +
+        trace_config_->data_source_stop_timeout_ms();
     task_runner_.PostDelayedTask(std::bind(&PerfettoCmd::OnTimeout, this),
                                  trace_timeout);
   }
@@ -768,9 +831,12 @@
     FinalizeTraceAndExit();  // Reached end of trace.
 }
 
-void PerfettoCmd::OnTracingDisabled() {
+void PerfettoCmd::OnTracingDisabled(const std::string& error) {
   LogUploadEvent(PerfettoStatsdAtom::kOnTracingDisabled);
 
+  if (!error.empty())
+    PERFETTO_ELOG("Service error: %s", error.c_str());
+
   if (trace_config_->write_into_file()) {
     // If write_into_file == true, at this point the passed file contains
     // already all the packets.
@@ -796,7 +862,7 @@
       bytes_written_ = static_cast<size_t>(sz);
   }
 
-  if (!dropbox_tag_.empty()) {
+  if (is_uploading_) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
     SaveTraceIntoDropboxAndIncidentOrCrash();
 #endif
@@ -817,7 +883,7 @@
 
 bool PerfettoCmd::OpenOutputFile() {
   base::ScopedFile fd;
-  if (!dropbox_tag_.empty()) {
+  if (is_uploading_) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
     fd = OpenDropboxTmpFile();
 #endif
@@ -947,11 +1013,18 @@
     const ObservableEvents& /*observable_events*/) {}
 
 void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  LogUploadEventAndroid(atom);
-#else
-  base::ignore_result(atom);
-#endif
+  if (!is_uploading_)
+    return;
+  base::Uuid uuid(uuid_);
+  android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb());
+}
+
+void PerfettoCmd::LogTriggerEvents(
+    PerfettoTriggerAtom atom,
+    const std::vector<std::string>& trigger_names) {
+  if (!is_uploading_)
+    return;
+  android_stats::MaybeLogTriggerEvents(atom, trigger_names);
 }
 
 int __attribute__((visibility("default")))
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index b017708..2b04f08 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -30,7 +30,7 @@
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/tracing/core/consumer.h"
 #include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
-#include "src/perfetto_cmd/perfetto_atoms.h"
+#include "src/android_stats/perfetto_atoms.h"
 #include "src/perfetto_cmd/rate_limiter.h"
 
 namespace perfetto {
@@ -48,7 +48,7 @@
   // perfetto::Consumer implementation.
   void OnConnect() override;
   void OnDisconnect() override;
-  void OnTracingDisabled() override;
+  void OnTracingDisabled(const std::string& error) override;
   void OnTraceData(std::vector<TracePacket>, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
@@ -78,11 +78,11 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   static base::ScopedFile OpenDropboxTmpFile();
   void SaveTraceIntoDropboxAndIncidentOrCrash();
-  void SaveOutputToDropboxOrCrash();
   void SaveOutputToIncidentTraceOrCrash();
-  void LogUploadEventAndroid(PerfettoStatsdAtom atom);
 #endif
   void LogUploadEvent(PerfettoStatsdAtom atom);
+  void LogTriggerEvents(PerfettoTriggerAtom atom,
+                        const std::vector<std::string>& trigger_names);
 
   base::UnixTaskRunner task_runner_;
 
@@ -95,7 +95,7 @@
 
   std::string trace_out_path_;
   base::EventFd ctrl_c_evt_;
-  std::string dropbox_tag_;
+  bool is_uploading_ = false;
   bool did_process_full_trace_ = false;
   uint64_t bytes_written_ = 0;
   std::string detach_key_;
@@ -104,6 +104,7 @@
   bool redetach_once_attached_ = false;
   bool query_service_ = false;
   bool query_service_output_raw_ = false;
+  bool bugreport_ = false;
   std::string uuid_;
 
   // How long we expect to trace for or 0 if the trace is indefinite.
diff --git a/src/perfetto_cmd/perfetto_cmd_android.cc b/src/perfetto_cmd/perfetto_cmd_android.cc
index f332073..6cdf4ad 100644
--- a/src/perfetto_cmd/perfetto_cmd_android.cc
+++ b/src/perfetto_cmd/perfetto_cmd_android.cc
@@ -21,75 +21,40 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/tracing/core/trace_config.h"
-#include "src/android_internal/dropbox_service.h"
 #include "src/android_internal/incident_service.h"
 #include "src/android_internal/lazy_library_loader.h"
-#include "src/android_internal/statsd_logging.h"
 
 namespace perfetto {
+namespace {
+
+constexpr int64_t kSendfileTimeoutNs = 10UL * 1000 * 1000 * 1000;  // 10s
+
+}  // namespace
 
 void PerfettoCmd::SaveTraceIntoDropboxAndIncidentOrCrash() {
-  PERFETTO_CHECK(!dropbox_tag_.empty());
-
-  bool use_dropbox = !trace_config_->incident_report_config().skip_dropbox();
-  bool use_incident =
-      !trace_config_->incident_report_config().destination_package().empty();
+  PERFETTO_CHECK(is_uploading_);
+  PERFETTO_CHECK(
+      !trace_config_->incident_report_config().destination_package().empty());
 
   if (bytes_written_ == 0) {
     LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
-    if (use_dropbox)
-      PERFETTO_LOG("Skipping write to dropbox. Empty trace.");
-    if (use_incident)
-      PERFETTO_LOG("Skipping write to incident. Empty trace.");
+    PERFETTO_LOG("Skipping write to incident. Empty trace.");
     return;
   }
 
-  // Otherwise, write to Dropbox unless there's a special override in the
-  // incident report config.
-  if (use_dropbox) {
-    SaveOutputToDropboxOrCrash();
-  }
+  // Save the trace as an incident.
+  SaveOutputToIncidentTraceOrCrash();
 
-  // Optionally save the trace as an incident. This is either in addition to, or
-  // instead of, the Dropbox write.
-  if (use_incident) {
-    SaveOutputToIncidentTraceOrCrash();
-
-    // Ask incidentd to create a report, which will read the file we just
-    // wrote.
-    const auto& cfg = trace_config_->incident_report_config();
-    PERFETTO_LAZY_LOAD(android_internal::StartIncidentReport, incident_fn);
-    PERFETTO_CHECK(incident_fn(cfg.destination_package().c_str(),
-                               cfg.destination_class().c_str(),
-                               cfg.privacy_level()));
-  }
-}
-
-void PerfettoCmd::SaveOutputToDropboxOrCrash() {
-  LogUploadEvent(PerfettoStatsdAtom::kUploadDropboxBegin);
-
-  PERFETTO_CHECK(fseek(*trace_out_stream_, 0, SEEK_SET) == 0);
-
-  // DropBox takes ownership of the file descriptor, so give it a duplicate.
-  // Also we need to give it a read-only copy of the fd or will hit a SELinux
-  // violation (about system_server ending up with a writable FD to our dir).
-  char fdpath[64];
-  sprintf(fdpath, "/proc/self/fd/%d", fileno(*trace_out_stream_));
-  base::ScopedFile read_only_fd(base::OpenFile(fdpath, O_RDONLY));
-  PERFETTO_CHECK(read_only_fd);
-
-  PERFETTO_LAZY_LOAD(android_internal::SaveIntoDropbox, dropbox_fn);
-  if (dropbox_fn(dropbox_tag_.c_str(), read_only_fd.release())) {
-    LogUploadEvent(PerfettoStatsdAtom::kUploadDropboxSuccess);
-    PERFETTO_LOG("Wrote %" PRIu64
-                 " bytes (before compression) into DropBox with tag %s",
-                 bytes_written_, dropbox_tag_.c_str());
-  } else {
-    LogUploadEvent(PerfettoStatsdAtom::kUploadDropboxFailure);
-    PERFETTO_FATAL("DropBox upload failed");
-  }
+  // Ask incidentd to create a report, which will read the file we just
+  // wrote.
+  const auto& cfg = trace_config_->incident_report_config();
+  PERFETTO_LAZY_LOAD(android_internal::StartIncidentReport, incident_fn);
+  PERFETTO_CHECK(incident_fn(cfg.destination_package().c_str(),
+                             cfg.destination_class().c_str(),
+                             cfg.privacy_level()));
 }
 
 // Open a staging file (unlinking the previous instance), copy the trace
@@ -107,14 +72,48 @@
 
   PERFETTO_CHECK(unlink(kTempIncidentTracePath) == 0 || errno == ENOENT);
 
+  // TODO(b/155024256) These should not be necessary (we flush when destroying
+  // packet writer and sendfile should ignore file offset) however they should
+  // not harm anything and it will help debug the linked issue.
+  PERFETTO_CHECK(fflush(*trace_out_stream_) == 0);
+  PERFETTO_CHECK(fseek(*trace_out_stream_, 0, SEEK_SET) == 0);
+
   // SELinux constrains the set of readers.
   base::ScopedFile staging_fd =
-      base::OpenFile(kTempIncidentTracePath, O_CREAT | O_RDWR, 0666);
+      base::OpenFile(kTempIncidentTracePath, O_CREAT | O_EXCL | O_RDWR, 0666);
   PERFETTO_CHECK(staging_fd);
+
+  int fd = fileno(*trace_out_stream_);
   off_t offset = 0;
-  auto wsize = sendfile(*staging_fd, fileno(*trace_out_stream_), &offset,
-                        static_cast<size_t>(bytes_written_));
-  PERFETTO_CHECK(wsize == static_cast<ssize_t>(bytes_written_));
+  size_t remaining = static_cast<size_t>(bytes_written_);
+
+  // Count time in terms of CPU to avoid timeouts due to suspend:
+  base::TimeNanos start = base::GetThreadCPUTimeNs();
+  for (;;) {
+    errno = 0;
+    PERFETTO_DCHECK(static_cast<size_t>(offset) + remaining == bytes_written_);
+    auto wsize = PERFETTO_EINTR(sendfile(*staging_fd, fd, &offset, remaining));
+    if (wsize < 0) {
+      PERFETTO_FATAL("sendfile() failed wsize=%zd, off=%" PRId64
+                     ", initial=%" PRIu64 ", remaining=%zu",
+                     wsize, static_cast<int64_t>(offset), bytes_written_,
+                     remaining);
+    }
+    remaining -= static_cast<size_t>(wsize);
+    if (remaining == 0) {
+      break;
+    }
+    base::TimeNanos now = base::GetThreadCPUTimeNs();
+    if (now < start || (now - start).count() > kSendfileTimeoutNs) {
+      PERFETTO_FATAL("sendfile() timed out wsize=%zd, off=%" PRId64
+                     ", initial=%" PRIu64
+                     ", remaining=%zu, start=%lld, now=%lld",
+                     wsize, static_cast<int64_t>(offset), bytes_written_,
+                     remaining, static_cast<long long int>(start.count()),
+                     static_cast<long long int>(now.count()));
+    }
+  }
+
   staging_fd.reset();
   PERFETTO_CHECK(rename(kTempIncidentTracePath, kIncidentTracePath) == 0);
   // Note: not calling fsync(2), as we're not interested in the file being
@@ -132,12 +131,4 @@
   return fd;
 }
 
-void PerfettoCmd::LogUploadEventAndroid(PerfettoStatsdAtom atom) {
-  if (dropbox_tag_.empty())
-    return;
-  PERFETTO_LAZY_LOAD(android_internal::StatsdLogEvent, log_event_fn);
-  base::Uuid uuid(uuid_);
-  log_event_fn(atom, uuid.lsb(), uuid.msb());
-}
-
 }  // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_config.descriptor.h b/src/perfetto_cmd/perfetto_config.descriptor.h
index 9f7467c..bafc01e 100644
--- a/src/perfetto_cmd/perfetto_config.descriptor.h
+++ b/src/perfetto_cmd/perfetto_config.descriptor.h
@@ -25,17 +25,17 @@
 // This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
 
 // SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
+// e5c244903aa00cad06faf3d126918306a7fe811e
 // SHA1(protos/perfetto/config/perfetto_config.proto)
-// a3e0d2a337fa889e6fe8f99420dac115bb8aeafd
+// e1a3a62626d7913d8cd34472458da738004a8592
 
 // This is the proto PerfettoConfig encoded as a ProtoFileDescriptor to allow
 // for reflection without libprotobuf full/non-lite protos.
 
 namespace perfetto {
 
-constexpr std::array<uint8_t, 18143> kPerfettoConfigDescriptor{
-    {0x0a, 0xdb, 0x8d, 0x01, 0x0a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+constexpr std::array<uint8_t, 20226> kPerfettoConfigDescriptor{
+    {0x0a, 0xfe, 0x9d, 0x01, 0x0a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
      0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x63, 0x6f,
      0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
      0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f,
@@ -299,41 +299,74 @@
      0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74,
      0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x70, 0x61,
      0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x46, 0x69, 0x6c,
-     0x74, 0x65, 0x72, 0x22, 0x6d, 0x0a, 0x0c, 0x43, 0x68, 0x72, 0x6f, 0x6d,
-     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x74,
-     0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72,
-     0x69, 0x76, 0x61, 0x63, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
-     0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x70, 0x72, 0x69, 0x76, 0x61,
-     0x63, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45,
-     0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xd6, 0x02, 0x0a, 0x0c, 0x46,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-     0x23, 0x0a, 0x0d, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x65, 0x76,
-     0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,
-     0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
-     0x12, 0x2b, 0x0a, 0x11, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63,
-     0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20,
-     0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43,
-     0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x0a,
-     0x0b, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x70, 0x70, 0x73,
-     0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x72, 0x61,
-     0x63, 0x65, 0x41, 0x70, 0x70, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75,
-     0x66, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62,
-     0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x75, 0x66, 0x66,
-     0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x62, 0x12, 0x26, 0x0a, 0x0f,
-     0x64, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64,
-     0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64,
-     0x72, 0x61, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73,
-     0x12, 0x55, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f,
-     0x73, 0x63, 0x68, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x46, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61,
-     0x63, 0x74, 0x53, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x53, 0x63,
-     0x68, 0x65, 0x64, 0x1a, 0x2e, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x61,
+     0x74, 0x65, 0x72, 0x22, 0xf3, 0x02, 0x0a, 0x0c, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x19, 0x70,
+     0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65,
+     0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x70, 0x72, 0x69, 0x76,
+     0x61, 0x63, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67,
+     0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x63,
+     0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x65,
+     0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74,
+     0x54, 0x6f, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e,
+     0x12, 0x55, 0x0a, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x70,
+     0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
+     0x0e, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6c, 0x69,
+     0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52,
+     0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x6f, 0x72,
+     0x69, 0x74, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x6a, 0x73, 0x6f, 0x6e, 0x5f,
+     0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f,
+     0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
+     0x52, 0x14, 0x6a, 0x73, 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4c,
+     0x61, 0x62, 0x65, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x41,
+     0x0a, 0x0e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x6f,
+     0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
+     0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x41, 0x43,
+     0x4b, 0x47, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a,
+     0x0e, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41,
+     0x54, 0x45, 0x44, 0x10, 0x02, 0x22, 0xdb, 0x03, 0x0a, 0x0c, 0x46, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23,
+     0x0a, 0x0d, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65,
+     0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12,
+     0x2b, 0x0a, 0x11, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x61,
+     0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
+     0x28, 0x09, 0x52, 0x10, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x61,
+     0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
+     0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18,
+     0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x41, 0x70, 0x70, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75, 0x66,
+     0x66, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18,
+     0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x75, 0x66, 0x66, 0x65,
+     0x72, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x62, 0x12, 0x26, 0x0a, 0x0f, 0x64,
+     0x72, 0x61, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f,
+     0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x72,
+     0x61, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12,
+     0x55, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73,
+     0x63, 0x68, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30,
+     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x46, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63,
+     0x74, 0x53, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x53, 0x63, 0x68,
+     0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
+     0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x73, 0x79, 0x6d, 0x73, 0x18, 0x0d, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69,
+     0x7a, 0x65, 0x4b, 0x73, 0x79, 0x6d, 0x73, 0x12, 0x5a, 0x0a, 0x2a, 0x69,
+     0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x73,
+     0x79, 0x6d, 0x73, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e,
+     0x6f, 0x75, 0x73, 0x6c, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x65,
+     0x73, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x26, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4b,
+     0x73, 0x79, 0x6d, 0x73, 0x53, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e,
+     0x6f, 0x75, 0x73, 0x6c, 0x79, 0x46, 0x6f, 0x72, 0x54, 0x65, 0x73, 0x74,
+     0x69, 0x6e, 0x67, 0x1a, 0x2e, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x61,
      0x63, 0x74, 0x53, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
      0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
      0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62,
@@ -398,1155 +431,1296 @@
      0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
      0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x72, 0x6f, 0x6f,
      0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63,
-     0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x22, 0x81, 0x03, 0x0a, 0x12,
-     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x61,
-     0x74, 0x74, 0x65, 0x72, 0x79, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x5f, 0x6d,
-     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x62, 0x61, 0x74,
-     0x74, 0x65, 0x72, 0x79, 0x50, 0x6f, 0x6c, 0x6c, 0x4d, 0x73, 0x12, 0x5e,
-     0x0a, 0x10, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e,
-     0x32, 0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0f, 0x62, 0x61, 0x74, 0x74, 0x65,
-     0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2e,
-     0x0a, 0x13, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x70, 0x6f,
-     0x77, 0x65, 0x72, 0x5f, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x11, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,
-     0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xb2,
-     0x01, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x41,
-     0x54, 0x54, 0x45, 0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45,
-     0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
-     0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x41, 0x54, 0x54, 0x45,
+     0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x22, 0xbb, 0x01, 0x0a, 0x0d,
+     0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x3d, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75,
+     0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x61, 0x62,
+     0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x43,
+     0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x22, 0x46, 0x0a, 0x06, 0x4f, 0x75, 0x74,
+     0x70, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x12, 0x4f, 0x55, 0x54, 0x50, 0x55,
+     0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
+     0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x50, 0x55,
+     0x54, 0x5f, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x11,
+     0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x5f, 0x53, 0x54, 0x44,
+     0x45, 0x52, 0x52, 0x10, 0x02, 0x22, 0x72, 0x0a, 0x11, 0x49, 0x6e, 0x74,
+     0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
+     0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x49,
+     0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
+     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0d,
+     0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x22, 0x81, 0x03, 0x0a, 0x12, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+     0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x12, 0x26, 0x0a, 0x0f, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x5f,
+     0x70, 0x6f, 0x6c, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x0d, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x50, 0x6f,
+     0x6c, 0x6c, 0x4d, 0x73, 0x12, 0x5e, 0x0a, 0x10, 0x62, 0x61, 0x74, 0x74,
+     0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
+     0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65,
+     0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x61, 0x74, 0x74,
+     0x65, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
+     0x0f, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e,
+     0x74, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x6f, 0x6c, 0x6c,
+     0x65, 0x63, 0x74, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x72, 0x61,
+     0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63,
+     0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52,
+     0x61, 0x69, 0x6c, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x0f, 0x42, 0x61, 0x74,
+     0x74, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
+     0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x5f,
+     0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+     0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a,
+     0x16, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55,
+     0x4e, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x48, 0x41, 0x52, 0x47, 0x45, 0x10,
+     0x01, 0x12, 0x24, 0x0a, 0x20, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59,
+     0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x50,
+     0x41, 0x43, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x45, 0x52, 0x43, 0x45, 0x4e,
+     0x54, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x41, 0x54, 0x54, 0x45,
      0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x43,
-     0x48, 0x41, 0x52, 0x47, 0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x42,
-     0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54,
-     0x45, 0x52, 0x5f, 0x43, 0x41, 0x50, 0x41, 0x43, 0x49, 0x54, 0x59, 0x5f,
-     0x50, 0x45, 0x52, 0x43, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1b, 0x0a,
-     0x17, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55,
-     0x4e, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x54,
-     0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52,
-     0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x55,
-     0x52, 0x52, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x56, 0x47, 0x10, 0x04, 0x22,
-     0x83, 0x04, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42,
-     0x0a, 0x06, 0x71, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x2e, 0x51, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x52, 0x06,
-     0x71, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x73, 0x63,
-     0x61, 0x6e, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72,
-     0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x73, 0x63, 0x61,
-     0x6e, 0x41, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65,
-     0x73, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13,
-     0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x68, 0x72,
-     0x65, 0x61, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x12,
-     0x70, 0x72, 0x6f, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x70,
-     0x6f, 0x6c, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x53, 0x74, 0x61, 0x74, 0x73, 0x50,
-     0x6f, 0x6c, 0x6c, 0x4d, 0x73, 0x12, 0x34, 0x0a, 0x17, 0x70, 0x72, 0x6f,
-     0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x61, 0x63, 0x68,
-     0x65, 0x5f, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x63, 0x53, 0x74, 0x61, 0x74,
-     0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x54, 0x74, 0x6c, 0x4d, 0x73, 0x12,
-     0x3c, 0x0a, 0x1b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x74, 0x68,
-     0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e,
-     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08,
-     0x52, 0x17, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x68, 0x72, 0x65,
-     0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x12, 0x43, 0x0a, 0x1f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
-     0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74,
-     0x65, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65,
-     0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1a, 0x74, 0x68, 0x72, 0x65,
-     0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x55,
-     0x0a, 0x06, 0x51, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x12, 0x16, 0x0a, 0x12,
-     0x51, 0x55, 0x49, 0x52, 0x4b, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
-     0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x14,
-     0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x49, 0x54,
-     0x49, 0x41, 0x4c, 0x5f, 0x44, 0x55, 0x4d, 0x50, 0x10, 0x01, 0x1a, 0x02,
-     0x08, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c,
-     0x45, 0x5f, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4d, 0x41, 0x4e, 0x44, 0x10,
-     0x02, 0x22, 0xc9, 0x07, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72,
-     0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a,
-     0x17, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x61, 0x6d, 0x70,
-     0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65,
-     0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x43, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x10,
-     0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x04, 0x52,
-     0x03, 0x70, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x68, 0x65, 0x61, 0x70,
-     0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x68, 0x65, 0x61,
-     0x70, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x05, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, 0x35, 0x0a, 0x17,
-     0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75,
-     0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6b, 0x62, 0x18,
-     0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x41, 0x6e,
-     0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75, 0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72,
-     0x79, 0x4b, 0x62, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x68,
-     0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x5f, 0x6d, 0x65, 0x6d,
-     0x6f, 0x72, 0x79, 0x5f, 0x6b, 0x62, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x14, 0x6d, 0x61, 0x78, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f,
-     0x66, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4b, 0x62, 0x12, 0x33,
-     0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x70, 0x72,
-     0x6f, 0x66, 0x64, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x73, 0x65, 0x63, 0x73,
-     0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x48,
-     0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x70, 0x75, 0x53,
-     0x65, 0x63, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x6b, 0x69, 0x70, 0x5f,
-     0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69,
-     0x78, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x73, 0x6b, 0x69,
-     0x70, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x50, 0x72, 0x65, 0x66, 0x69,
-     0x78, 0x12, 0x6b, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75,
+     0x55, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b,
+     0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x5f, 0x43, 0x4f, 0x55, 0x4e,
+     0x54, 0x45, 0x52, 0x5f, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x5f,
+     0x41, 0x56, 0x47, 0x10, 0x04, 0x22, 0x83, 0x04, 0x0a, 0x12, 0x50, 0x72,
+     0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x06, 0x71, 0x75, 0x69, 0x72,
+     0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74,
+     0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x51, 0x75,
+     0x69, 0x72, 0x6b, 0x73, 0x52, 0x06, 0x71, 0x75, 0x69, 0x72, 0x6b, 0x73,
+     0x12, 0x3c, 0x0a, 0x1b, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x61, 0x6c, 0x6c,
+     0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x6f,
+     0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x17, 0x73, 0x63, 0x61, 0x6e, 0x41, 0x6c, 0x6c, 0x50, 0x72,
+     0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x4f, 0x6e, 0x53, 0x74, 0x61,
+     0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
+     0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
+     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x63,
+     0x6f, 0x72, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x4e, 0x61, 0x6d,
+     0x65, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x63, 0x5f, 0x73,
+     0x74, 0x61, 0x74, 0x73, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x5f, 0x6d, 0x73,
+     0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x50, 0x6f, 0x6c, 0x6c, 0x4d, 0x73, 0x12,
+     0x34, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74,
+     0x73, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x74, 0x74, 0x6c, 0x5f,
+     0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x70, 0x72,
+     0x6f, 0x63, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65,
+     0x54, 0x74, 0x6c, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x72, 0x65, 0x63,
+     0x6f, 0x72, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74,
+     0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65,
+     0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x72, 0x65, 0x63, 0x6f,
+     0x72, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
+     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x43, 0x0a, 0x1f, 0x74,
+     0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69,
+     0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x61, 0x63, 0x68,
+     0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x1a, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
+     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65,
+     0x53, 0x69, 0x7a, 0x65, 0x22, 0x55, 0x0a, 0x06, 0x51, 0x75, 0x69, 0x72,
+     0x6b, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x51, 0x55, 0x49, 0x52, 0x4b, 0x53,
+     0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+     0x10, 0x00, 0x12, 0x1c, 0x0a, 0x14, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c,
+     0x45, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x44, 0x55,
+     0x4d, 0x50, 0x10, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x15, 0x0a, 0x11,
+     0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x4f, 0x4e, 0x5f, 0x44,
+     0x45, 0x4d, 0x41, 0x4e, 0x44, 0x10, 0x02, 0x22, 0xa8, 0x08, 0x0a, 0x0f,
+     0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x61, 0x6d, 0x70, 0x6c,
+     0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+     0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
+     0x52, 0x15, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e,
+     0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12,
+     0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63,
+     0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
+     0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6d, 0x64,
+     0x6c, 0x69, 0x6e, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18,
+     0x04, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x14,
+     0x0a, 0x05, 0x68, 0x65, 0x61, 0x70, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28,
+     0x09, 0x52, 0x05, 0x68, 0x65, 0x61, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x12,
+     0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08,
+     0x52, 0x11, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x6c, 0x6c, 0x6f,
+     0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x68,
+     0x65, 0x61, 0x70, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67,
+     0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x18, 0x16,
+     0x20, 0x03, 0x28, 0x04, 0x52, 0x15, 0x68, 0x65, 0x61, 0x70, 0x53, 0x61,
+     0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76,
+     0x61, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x6c, 0x6c, 0x5f, 0x68,
+     0x65, 0x61, 0x70, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
+     0x61, 0x6c, 0x6c, 0x48, 0x65, 0x61, 0x70, 0x73, 0x12, 0x10, 0x0a, 0x03,
+     0x61, 0x6c, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61,
+     0x6c, 0x6c, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x6e,
+     0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x6f,
+     0x72, 0x79, 0x5f, 0x6b, 0x62, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52,
+     0x14, 0x6d, 0x69, 0x6e, 0x41, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75,
+     0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4b, 0x62, 0x12, 0x35, 0x0a,
+     0x17, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f,
+     0x66, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6b, 0x62,
+     0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x48,
+     0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x4d, 0x65, 0x6d, 0x6f,
+     0x72, 0x79, 0x4b, 0x62, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f,
+     0x68, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x5f, 0x63, 0x70,
+     0x75, 0x5f, 0x73, 0x65, 0x63, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04,
+     0x52, 0x13, 0x6d, 0x61, 0x78, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f,
+     0x66, 0x64, 0x43, 0x70, 0x75, 0x53, 0x65, 0x63, 0x73, 0x12, 0x2c, 0x0a,
+     0x12, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
+     0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x07, 0x20, 0x03, 0x28,
+     0x09, 0x52, 0x10, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x79, 0x6d, 0x62, 0x6f,
+     0x6c, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x6b, 0x0a, 0x16, 0x63,
+     0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x75,
+     0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20,
+     0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65,
+     0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73,
+     0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x14,
+     0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75,
+     0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x10,
+     0x73, 0x68, 0x6d, 0x65, 0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62,
+     0x79, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e,
+     0x73, 0x68, 0x6d, 0x65, 0x6d, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74,
+     0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
+     0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08,
+     0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6c, 0x69, 0x65, 0x6e,
+     0x74, 0x12, 0x35, 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x63,
+     0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75,
+     0x74, 0x5f, 0x75, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14,
+     0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54,
+     0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
+     0x6e, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x0a,
+     0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x53, 0x74, 0x61, 0x72,
+     0x74, 0x75, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x72, 0x75,
+     0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x09, 0x6e, 0x6f, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x1e,
+     0x0a, 0x0b, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x61,
+     0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x75, 0x6d,
+     0x70, 0x41, 0x74, 0x4d, 0x61, 0x78, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69,
+     0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x74,
+     0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x6f,
+     0x72, 0x6b, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x36,
+     0x0a, 0x17, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x66,
+     0x6f, 0x72, 0x6b, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f,
+     0x6e, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x64, 0x69, 0x73,
+     0x61, 0x62, 0x6c, 0x65, 0x56, 0x66, 0x6f, 0x72, 0x6b, 0x44, 0x65, 0x74,
+     0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x64, 0x0a, 0x14, 0x43, 0x6f,
+     0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x75,
+     0x6d, 0x70, 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x18,
+     0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x64, 0x75, 0x6d, 0x70, 0x50,
+     0x68, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x75,
+     0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f,
+     0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x75,
+     0x6d, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73,
+     0x4a, 0x04, 0x08, 0x0c, 0x10, 0x0d, 0x22, 0x9a, 0x03, 0x0a, 0x0f, 0x4a,
+     0x61, 0x76, 0x61, 0x48, 0x70, 0x72, 0x6f, 0x66, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
+     0x73, 0x5f, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20,
+     0x03, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x43, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70,
+     0x69, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x70, 0x69,
+     0x64, 0x12, 0x6b, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75,
      0x6f, 0x75, 0x73, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e,
+     0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e,
      0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66,
-     0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74,
+     0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x70, 0x72, 0x6f,
+     0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74,
      0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f,
      0x6e, 0x66, 0x69, 0x67, 0x52, 0x14, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e,
      0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6d, 0x65, 0x6d, 0x5f,
-     0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x08,
-     0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x6d, 0x65, 0x6d, 0x53,
-     0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c,
-     0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
-     0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63,
-     0x6b, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x62,
-     0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,
-     0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x0e,
-     0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x43,
-     0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
-     0x55, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x73, 0x74, 0x61,
-     0x72, 0x74, 0x75, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
-     0x6e, 0x6f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x1d, 0x0a,
-     0x0a, 0x6e, 0x6f, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18,
-     0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x52, 0x75, 0x6e,
-     0x6e, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x64, 0x6c, 0x65,
-     0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-     0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x64, 0x6c, 0x65,
-     0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
-     0x1e, 0x0a, 0x0b, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x61, 0x74, 0x5f, 0x6d,
-     0x61, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x75,
-     0x6d, 0x70, 0x41, 0x74, 0x4d, 0x61, 0x78, 0x12, 0x32, 0x0a, 0x15, 0x64,
-     0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x5f,
-     0x74, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x12, 0x20, 0x01,
-     0x28, 0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46,
-     0x6f, 0x72, 0x6b, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x12,
-     0x36, 0x0a, 0x17, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76,
-     0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69,
-     0x6f, 0x6e, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x64, 0x69,
-     0x73, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x66, 0x6f, 0x72, 0x6b, 0x44, 0x65,
-     0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x64, 0x0a, 0x14, 0x43,
-     0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d,
-     0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x22, 0x0a, 0x0d, 0x64,
-     0x75, 0x6d, 0x70, 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73,
-     0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x64, 0x75, 0x6d, 0x70,
-     0x50, 0x68, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64,
-     0x75, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
-     0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64,
-     0x75, 0x6d, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d,
-     0x73, 0x22, 0xf5, 0x02, 0x0a, 0x0f, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x70,
-     0x72, 0x6f, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, 0x0a,
-     0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6d, 0x64,
-     0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6d, 0x64, 0x6c, 0x69,
-     0x6e, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20,
-     0x03, 0x28, 0x04, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x6b, 0x0a, 0x16,
-     0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x64,
-     0x75, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a,
-     0x61, 0x76, 0x61, 0x48, 0x70, 0x72, 0x6f, 0x66, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75,
-     0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
-     0x14, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44,
-     0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a,
-     0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f,
-     0x75, 0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x6b, 0x62,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x41,
-     0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75, 0x73, 0x4d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x4b, 0x62, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x75, 0x6d, 0x70,
-     0x5f, 0x73, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
-     0x52, 0x09, 0x64, 0x75, 0x6d, 0x70, 0x53, 0x6d, 0x61, 0x70, 0x73, 0x1a,
-     0x64, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75,
-     0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-     0x22, 0x0a, 0x0d, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x70, 0x68, 0x61, 0x73,
-     0x65, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b,
-     0x64, 0x75, 0x6d, 0x70, 0x50, 0x68, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x12,
-     0x28, 0x0a, 0x10, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65,
-     0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x0e, 0x64, 0x75, 0x6d, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72,
-     0x76, 0x61, 0x6c, 0x4d, 0x73, 0x22, 0xd4, 0x03, 0x0a, 0x0f, 0x50, 0x65,
-     0x72, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x70, 0x75,
-     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c,
-     0x43, 0x70, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x73, 0x61, 0x6d, 0x70,
-     0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e,
-     0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x73, 0x61,
-     0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65,
-     0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a, 0x1a, 0x72, 0x69, 0x6e, 0x67, 0x5f,
-     0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f,
-     0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x16, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x66,
-     0x66, 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x50, 0x65, 0x72, 0x69, 0x6f,
-     0x64, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x69, 0x6e, 0x67, 0x5f,
-     0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x72, 0x69, 0x6e, 0x67,
-     0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12,
-     0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x69,
-     0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x09, 0x74, 0x61, 0x72,
-     0x67, 0x65, 0x74, 0x50, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x61,
-     0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65,
-     0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67,
-     0x65, 0x74, 0x43, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1f, 0x0a,
-     0x0b, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x69, 0x64,
-     0x18, 0x06, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x6c,
-     0x75, 0x64, 0x65, 0x50, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78,
-     0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e,
-     0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x63,
-     0x6c, 0x75, 0x64, 0x65, 0x43, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12,
-     0x3f, 0x0a, 0x1c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x64, 0x65,
-     0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d,
-     0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x44, 0x65, 0x73,
-     0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f,
-     0x75, 0x74, 0x4d, 0x73, 0x12, 0x3e, 0x0a, 0x1c, 0x75, 0x6e, 0x77, 0x69,
-     0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x65,
-     0x61, 0x72, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73,
-     0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18, 0x75, 0x6e, 0x77, 0x69,
-     0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x65, 0x61, 0x72,
-     0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x22, 0xf3, 0x03, 0x0a,
-     0x0e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x6d, 0x69, 0x6e,
-     0x66, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x65, 0x6d, 0x69,
-     0x6e, 0x66, 0x6f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12,
-     0x4b, 0x0a, 0x10, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-     0x0e, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x69,
-     0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
-     0x0f, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e,
-     0x74, 0x65, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x76, 0x6d, 0x73, 0x74,
+     0x69, 0x67, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x6e,
+     0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x6f,
+     0x72, 0x79, 0x5f, 0x6b, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
+     0x14, 0x6d, 0x69, 0x6e, 0x41, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x6f, 0x75,
+     0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4b, 0x62, 0x12, 0x1d, 0x0a,
+     0x0a, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x73, 0x6d, 0x61, 0x70, 0x73, 0x18,
+     0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x75, 0x6d, 0x70, 0x53,
+     0x6d, 0x61, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f,
+     0x72, 0x65, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20,
+     0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64,
+     0x54, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x64, 0x0a, 0x14, 0x43, 0x6f, 0x6e,
+     0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x75, 0x6d,
+     0x70, 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x01,
+     0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x64, 0x75, 0x6d, 0x70, 0x50, 0x68,
+     0x61, 0x73, 0x65, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x75, 0x6d,
+     0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d,
+     0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x75, 0x6d,
+     0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x22,
+     0xe4, 0x04, 0x0a, 0x0f, 0x50, 0x65, 0x72, 0x66, 0x45, 0x76, 0x65, 0x6e,
+     0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61,
+     0x6c, 0x6c, 0x5f, 0x63, 0x70, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x43, 0x70, 0x75, 0x73, 0x12, 0x2d,
+     0x0a, 0x12, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66,
+     0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x11, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67,
+     0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x3a, 0x0a,
+     0x1a, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64,
+     0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x72,
+     0x69, 0x6e, 0x67, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x65, 0x61,
+     0x64, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x2a, 0x0a,
+     0x11, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0f, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6b, 0x65, 0x72,
+     0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c,
+     0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61,
+     0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x04, 0x20, 0x03,
+     0x28, 0x05, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x69,
+     0x64, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
+     0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28,
+     0x09, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6d, 0x64,
+     0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x63, 0x6c,
+     0x75, 0x64, 0x65, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x06, 0x20, 0x03, 0x28,
+     0x05, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x69,
+     0x64, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,
+     0x5f, 0x63, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x03,
+     0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43,
+     0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x64,
+     0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6d, 0x64,
+     0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b,
+     0x20, 0x01, 0x28, 0x0d, 0x52, 0x16, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69,
+     0x6f, 0x6e, 0x61, 0x6c, 0x43, 0x6d, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x43,
+     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f,
+     0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72,
+     0x79, 0x5f, 0x6b, 0x62, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11,
+     0x6d, 0x61, 0x78, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x4d, 0x65, 0x6d,
+     0x6f, 0x72, 0x79, 0x4b, 0x62, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x65, 0x6d,
+     0x6f, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+     0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d,
+     0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 0x72, 0x65, 0x6d,
+     0x6f, 0x74, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
+     0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x3e,
+     0x0a, 0x1c, 0x75, 0x6e, 0x77, 0x69, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61,
+     0x74, 0x65, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x70, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x18, 0x75, 0x6e, 0x77, 0x69, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64,
+     0x4d, 0x73, 0x22, 0xf3, 0x03, 0x0a, 0x0e, 0x53, 0x79, 0x73, 0x53, 0x74,
+     0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2a, 0x0a,
+     0x11, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x70, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0f, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x50, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x4b, 0x0a, 0x10, 0x6d, 0x65, 0x6d,
+     0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
+     0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x4d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75,
+     0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0f, 0x6d, 0x65, 0x6d, 0x69, 0x6e,
+     0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x28,
+     0x0a, 0x10, 0x76, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0e, 0x76, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x50, 0x65, 0x72, 0x69,
+     0x6f, 0x64, 0x4d, 0x73, 0x12, 0x48, 0x0a, 0x0f, 0x76, 0x6d, 0x73, 0x74,
+     0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18,
+     0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x56, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
+     0x72, 0x73, 0x52, 0x0e, 0x76, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f,
+     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74,
      0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x76, 0x6d, 0x73, 0x74,
-     0x61, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x48,
-     0x0a, 0x0f, 0x76, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32,
-     0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x6d, 0x73, 0x74, 0x61, 0x74,
-     0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x76, 0x6d,
-     0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
-     0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
-     0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64,
-     0x4d, 0x73, 0x12, 0x51, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
-     0x0e, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53,
-     0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
-     0x0c, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x73, 0x22, 0x7b, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
-     0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x43, 0x50, 0x55, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x10, 0x01, 0x12,
-     0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x49, 0x52, 0x51, 0x5f,
-     0x43, 0x4f, 0x55, 0x4e, 0x54, 0x53, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x53, 0x4f, 0x46, 0x54, 0x49, 0x52, 0x51,
-     0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x53, 0x10, 0x03, 0x12, 0x13, 0x0a,
-     0x0f, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x4b, 0x5f, 0x43,
-     0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x22, 0x9e, 0x06, 0x0a, 0x0a, 0x54,
-     0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a,
-     0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65,
-     0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35,
-     0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-     0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e,
-     0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78,
-     0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53,
-     0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x65,
-     0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x65, 0x65,
-     0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-     0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
-     0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65,
-     0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x62, 0x61, 0x74,
-     0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
-     0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x73, 0x65,
-     0x6e, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x52, 0x65, 0x67,
-     0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x0c, 0x64, 0x75, 0x6d,
-     0x6d, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x65,
-     0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x75, 0x6d,
-     0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x0b, 0x64, 0x75,
-     0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0xfb, 0x03,
-     0x0a, 0x0b, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64,
-     0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75,
-     0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
-     0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32,
-     0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e,
-     0x74, 0x33, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66,
-     0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x21, 0x0a,
-     0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36,
-     0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x69, 0x65,
-     0x6c, 0x64, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x1f, 0x0a, 0x0b,
-     0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64,
-     0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x65,
-     0x6c, 0x64, 0x5f, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x46,
-     0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x69,
-     0x65, 0x6c, 0x64, 0x5f, 0x73, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x10, 0x52, 0x0d, 0x66, 0x69, 0x65, 0x6c,
-     0x64, 0x53, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x12, 0x23, 0x0a,
-     0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x78, 0x65, 0x64,
-     0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x07, 0x52, 0x0c, 0x66, 0x69,
-     0x65, 0x6c, 0x64, 0x46, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x12, 0x25,
-     0x0a, 0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x66, 0x69, 0x78,
-     0x65, 0x64, 0x33, 0x32, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0f, 0x52, 0x0d,
-     0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x66, 0x69, 0x78, 0x65, 0x64, 0x33,
-     0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x64,
-     0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52,
-     0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-     0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x6c,
-     0x6f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x66,
-     0x69, 0x65, 0x6c, 0x64, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x21, 0x0a,
-     0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x6e, 0x74, 0x36,
-     0x34, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x12, 0x52, 0x0b, 0x66, 0x69, 0x65,
-     0x6c, 0x64, 0x53, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x21, 0x0a, 0x0c,
-     0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x6e, 0x74, 0x33, 0x32,
-     0x18, 0x0c, 0x20, 0x01, 0x28, 0x11, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c,
-     0x64, 0x53, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66,
-     0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18,
-     0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64,
-     0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69,
-     0x65, 0x6c, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0e, 0x20,
-     0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x79,
-     0x74, 0x65, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x63,
-     0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x12, 0x2f, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
-     0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18,
-     0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62,
-     0x6c, 0x65, 0x64, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65,
-     0x73, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
-     0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18,
-     0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x6e, 0x61, 0x62, 0x6c,
-     0x65, 0x64, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73,
-     0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
-     0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
-     0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x54, 0x61, 0x67,
-     0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
-     0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52,
-     0x0b, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x54, 0x61, 0x67, 0x73,
-     0x22, 0xf2, 0x0c, 0x0a, 0x10, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a,
-     0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72,
-     0x67, 0x65, 0x74, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
-     0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x74,
-     0x72, 0x61, 0x63, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x74,
-     0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x54, 0x69, 0x6d,
-     0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x65, 0x6e,
-     0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x67,
-     0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45,
-     0x78, 0x74, 0x72, 0x61, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69,
-     0x6c, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e,
-     0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x74, 0x72, 0x61, 0x63,
-     0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
-     0x12, 0x46, 0x0a, 0x0d, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x1d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x46, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0c,
-     0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x12, 0x50, 0x0a, 0x11, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x66, 0x69,
-     0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x66, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e,
-     0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x69, 0x6e, 0x6f, 0x64, 0x65,
-     0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59,
-     0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x74,
-     0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x67,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x12, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4d, 0x0a, 0x10, 0x73, 0x79, 0x73,
-     0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65,
+     0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74,
+     0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x51, 0x0a, 0x0d,
+     0x73, 0x74, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
+     0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x70, 0x65,
      0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
      0x73, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0e, 0x73, 0x79,
-     0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x12, 0x4f, 0x0a, 0x10, 0x68, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66,
-     0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x69, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61,
-     0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x42, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x68, 0x65, 0x61, 0x70, 0x70, 0x72,
-     0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a,
-     0x11, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x70, 0x72, 0x6f, 0x66, 0x5f,
-     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48,
-     0x70, 0x72, 0x6f, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02,
-     0x28, 0x01, 0x52, 0x0f, 0x6a, 0x61, 0x76, 0x61, 0x48, 0x70, 0x72, 0x6f,
-     0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x14, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72,
-     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6a, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x12, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x12, 0x53, 0x0a, 0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x18, 0x6b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72,
+     0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75,
+     0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x43,
+     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0x7b, 0x0a, 0x0c, 0x53,
+     0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12,
+     0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+     0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a,
+     0x0e, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x50, 0x55, 0x5f, 0x54, 0x49,
+     0x4d, 0x45, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x49, 0x52, 0x51, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x53,
+     0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x53,
+     0x4f, 0x46, 0x54, 0x49, 0x52, 0x51, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54,
+     0x53, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x46, 0x4f, 0x52, 0x4b, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04,
+     0x22, 0x9e, 0x06, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61,
+     0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43,
+     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f,
+     0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72,
+     0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+     0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12,
+     0x12, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x04, 0x73, 0x65, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d,
+     0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
+     0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61,
+     0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65,
+     0x6e, 0x64, 0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x5f,
+     0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x42, 0x61, 0x74, 0x63,
+     0x68, 0x4f, 0x6e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12,
+     0x4a, 0x0a, 0x0c, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x5f, 0x66, 0x69, 0x65,
+     0x6c, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x2e, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c,
+     0x64, 0x73, 0x52, 0x0b, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65,
+     0x6c, 0x64, 0x73, 0x1a, 0xfb, 0x03, 0x0a, 0x0b, 0x44, 0x75, 0x6d, 0x6d,
+     0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69,
+     0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e,
+     0x74, 0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28,
+     0x04, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x55, 0x69, 0x6e, 0x74,
+     0x36, 0x34, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
+     0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
+     0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12,
+     0x23, 0x0a, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x78,
+     0x65, 0x64, 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c,
+     0x66, 0x69, 0x65, 0x6c, 0x64, 0x46, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34,
+     0x12, 0x25, 0x0a, 0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x66,
+     0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x10,
+     0x52, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x66, 0x69, 0x78, 0x65,
+     0x64, 0x36, 0x34, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x5f, 0x66, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01,
+     0x28, 0x07, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x46, 0x69, 0x78,
+     0x65, 0x64, 0x33, 0x32, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x69, 0x65, 0x6c,
+     0x64, 0x5f, 0x73, 0x66, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x18, 0x08,
+     0x20, 0x01, 0x28, 0x0f, 0x52, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53,
+     0x66, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x5f, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18,
+     0x09, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69,
+     0x65, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x0a, 0x20,
+     0x01, 0x28, 0x02, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x46, 0x6c,
+     0x6f, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x5f, 0x73, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x0b, 0x20, 0x01, 0x28,
+     0x12, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x69, 0x6e, 0x74,
+     0x36, 0x34, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
+     0x73, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x11,
+     0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x69, 0x6e, 0x74, 0x33,
+     0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73,
+     0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52,
+     0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
+     0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x79,
+     0x74, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xba, 0x01,
+     0x0a, 0x10, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x69,
+     0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67,
+     0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
+     0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x61, 0x74,
+     0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x65,
+     0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67,
+     0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+     0x11, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x61, 0x74, 0x65,
+     0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69,
+     0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18,
+     0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62,
+     0x6c, 0x65, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x65,
+     0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18,
+     0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x6e, 0x61, 0x62, 0x6c,
+     0x65, 0x64, 0x54, 0x61, 0x67, 0x73, 0x22, 0xc5, 0x0d, 0x0a, 0x10, 0x44,
+     0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+     0x23, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x75,
+     0x66, 0x66, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c,
+     0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x75,
+     0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20,
+     0x01, 0x28, 0x0d, 0x52, 0x0f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x44, 0x75,
+     0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f,
+     0x73, 0x74, 0x6f, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
+     0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73,
+     0x74, 0x6f, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73,
+     0x12, 0x36, 0x0a, 0x17, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65,
+     0x78, 0x74, 0x72, 0x61, 0x5f, 0x67, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61,
+     0x69, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x65,
+     0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x72, 0x61, 0x47, 0x75,
+     0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2c, 0x0a, 0x12,
+     0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73,
+     0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04,
+     0x52, 0x10, 0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73,
+     0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x46, 0x0a, 0x0d, 0x66, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
+     0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x46, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x42, 0x02, 0x28, 0x01, 0x52, 0x0c, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x11, 0x69, 0x6e,
+     0x6f, 0x64, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52,
+     0x0f, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63,
+     0x65, 0x73, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
+     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42,
+     0x02, 0x28, 0x01, 0x52, 0x12, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x4d, 0x0a, 0x10, 0x73, 0x79, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x68, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x53,
+     0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02,
+     0x28, 0x01, 0x52, 0x0e, 0x73, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x10, 0x68, 0x65,
+     0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0f,
+     0x68, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x11, 0x6a, 0x61, 0x76, 0x61, 0x5f,
+     0x68, 0x70, 0x72, 0x6f, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72,
      0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6f, 0x67, 0x43,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x10, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x18, 0x6c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65,
+     0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x70, 0x72, 0x6f, 0x66, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x6a, 0x61,
+     0x76, 0x61, 0x48, 0x70, 0x72, 0x6f, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x59, 0x0a, 0x14, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+     0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65,
      0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x47, 0x70, 0x75, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x10,
-     0x67, 0x70, 0x75, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x14, 0x70, 0x61, 0x63, 0x6b,
-     0x61, 0x67, 0x65, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x18, 0x6d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x73, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42,
-     0x02, 0x28, 0x01, 0x52, 0x12, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x73, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-     0x50, 0x0a, 0x11, 0x70, 0x65, 0x72, 0x66, 0x5f, 0x65, 0x76, 0x65, 0x6e,
-     0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6f, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x65, 0x72,
-     0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x42, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x45, 0x76,
-     0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a,
-     0x14, 0x76, 0x75, 0x6c, 0x6b, 0x61, 0x6e, 0x5f, 0x6d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x70, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x75,
-     0x6c, 0x6b, 0x61, 0x6e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x12, 0x76, 0x75,
-     0x6c, 0x6b, 0x61, 0x6e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x18, 0x71, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70,
+     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77,
+     0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01,
+     0x52, 0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77,
+     0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a, 0x12,
+     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x5f,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
+     0x69, 0x64, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42,
+     0x02, 0x28, 0x01, 0x52, 0x10, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+     0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53, 0x0a,
+     0x12, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6c, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x47, 0x70, 0x75, 0x43,
+     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x42, 0x02, 0x28, 0x01, 0x52, 0x10, 0x67, 0x70, 0x75, 0x43, 0x6f, 0x75,
+     0x6e, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59,
+     0x0a, 0x14, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x6c,
+     0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x6d,
+     0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
+     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50,
+     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x12, 0x70,
+     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x11, 0x70, 0x65, 0x72,
+     0x66, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x18, 0x6f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70,
      0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e,
-     0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52,
-     0x10, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x6c, 0x0a, 0x1b, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x5f,
-     0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x18, 0x72, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x6c, 0x6c,
-     0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x18, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a, 0x0d, 0x63,
-     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x52, 0x0c, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x6c, 0x65, 0x67, 0x61, 0x63,
-     0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0xe8, 0x07, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3d, 0x0a, 0x0b, 0x66, 0x6f, 0x72,
-     0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x18, 0xe9, 0x07, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x65,
-     0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x66, 0x6f,
-     0x72, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x4a, 0x0b, 0x08, 0xff,
-     0xff, 0xff, 0x7f, 0x10, 0x80, 0x80, 0x80, 0x80, 0x01, 0x22, 0x9a, 0x1d,
-     0x0a, 0x0b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x12, 0x43, 0x0a, 0x07, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73,
-     0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x2e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x52, 0x07, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x12, 0x4a,
-     0x0a, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x52, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x73, 0x12, 0x60, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69,
-     0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x44, 0x61,
-     0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x12, 0x62, 0x75,
-     0x69, 0x6c, 0x74, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61,
-     0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d,
-     0x73, 0x12, 0x36, 0x0a, 0x17, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x67, 0x75, 0x61, 0x72, 0x64, 0x72,
-     0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15,
-     0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x72, 0x61, 0x47,
-     0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a,
-     0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x6d, 0x6f,
-     0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x2e, 0x4c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x4d,
-     0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x6f,
-     0x64, 0x65, 0x12, 0x49, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63,
-     0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e,
+     0x6f, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x0f,
+     0x70, 0x65, 0x72, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x14, 0x76, 0x75, 0x6c, 0x6b, 0x61,
+     0x6e, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x18, 0x70, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e,
      0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64,
-     0x75, 0x63, 0x65, 0x72, 0x73, 0x12, 0x54, 0x0a, 0x0f, 0x73, 0x74, 0x61,
-     0x74, 0x73, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-     0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64,
-     0x61, 0x74, 0x61, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x77,
-     0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x6f, 0x5f, 0x66, 0x69,
-     0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x77, 0x72,
-     0x69, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x6f, 0x46, 0x69, 0x6c, 0x65, 0x12,
-     0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x70, 0x61,
-     0x74, 0x68, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75,
-     0x74, 0x70, 0x75, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2f, 0x0a, 0x14,
-     0x66, 0x69, 0x6c, 0x65, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x70,
-     0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x11, 0x66, 0x69, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74,
-     0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x2d, 0x0a,
-     0x13, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69,
-     0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x46, 0x69, 0x6c, 0x65, 0x53,
-     0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x60, 0x0a, 0x13,
-     0x67, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x5f, 0x6f, 0x76,
-     0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
-     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x47, 0x75, 0x61, 0x72,
-     0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
-     0x65, 0x73, 0x52, 0x12, 0x67, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69,
-     0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x12, 0x25,
-     0x0a, 0x0e, 0x64, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x73,
-     0x74, 0x61, 0x72, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
-     0x64, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x53, 0x74, 0x61, 0x72,
-     0x74, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x70,
-     0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x0d, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x50, 0x65, 0x72,
-     0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d,
-     0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12,
-     0x3c, 0x0a, 0x1b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x17, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
-     0x53, 0x74, 0x6f, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d,
-     0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x75, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x54, 0x72, 0x61,
-     0x63, 0x65, 0x75, 0x72, 0x12, 0x51, 0x0a, 0x0e, 0x74, 0x72, 0x69, 0x67,
-     0x67, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x11,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54,
-     0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54,
-     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x52, 0x0d, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76,
-     0x61, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73,
-     0x18, 0x12, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x61, 0x74, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73,
-     0x12, 0x6d, 0x0a, 0x18, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e,
-     0x74, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65,
-     0x6e, 0x74, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x52, 0x16, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65,
-     0x6e, 0x74, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
-     0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-     0x74, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x18, 0x13, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72,
-     0x42, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67,
-     0x12, 0x2e, 0x0a, 0x13, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x73,
-     0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-     0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x6e, 0x69, 0x71, 0x75,
-     0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65,
-     0x12, 0x57, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73,
-     0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x18, 0x20, 0x01,
-     0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x74, 0x6f, 0x73, 0x2e, 0x56, 0x75, 0x6c, 0x6b, 0x61, 0x6e, 0x4d, 0x65,
+     0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02,
+     0x28, 0x01, 0x52, 0x12, 0x76, 0x75, 0x6c, 0x6b, 0x61, 0x6e, 0x4d, 0x65,
+     0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x53,
+     0x0a, 0x12, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x71, 0x20, 0x01,
+     0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
      0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
-     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d,
-     0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65,
-     0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f,
-     0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x67, 0x0a, 0x16, 0x69, 0x6e, 0x63,
-     0x69, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74,
-     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x31, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x42, 0x02, 0x28, 0x01, 0x52, 0x10, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x6c, 0x0a, 0x1b, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70,
+     0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x72, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
+     0x69, 0x64, 0x50, 0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x02, 0x28, 0x01, 0x52,
+     0x18, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x6c, 0x6c,
+     0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x42, 0x0a, 0x0d, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x1d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x63, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x51, 0x0a,
+     0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x73, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65,
+     0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x52, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74,
+     0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d,
+     0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x65,
+     0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3d,
+     0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e,
+     0x67, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e,
+     0x67, 0x4a, 0x0b, 0x08, 0xff, 0xff, 0xff, 0x7f, 0x10, 0x80, 0x80, 0x80,
+     0x80, 0x01, 0x22, 0xc3, 0x1d, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x63, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x07, 0x62, 0x75,
+     0x66, 0x66, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x66, 0x66,
+     0x65, 0x72, 0x73, 0x12, 0x4a, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x5f,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
      0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
-     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x63, 0x69,
-     0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x69, 0x64, 0x65,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61,
+     0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0b, 0x64, 0x61, 0x74, 0x61,
+     0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x60, 0x0a, 0x14, 0x62,
+     0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x69, 0x6c,
+     0x74, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63,
+     0x65, 0x52, 0x12, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x44, 0x61,
+     0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a,
+     0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73,
+     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61,
+     0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x65, 0x6e,
+     0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x67,
+     0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45,
+     0x78, 0x74, 0x72, 0x61, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69,
+     0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f,
+     0x77, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+     0x0e, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x6f, 0x63, 0x6b,
+     0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65, 0x72,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x64,
+     0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x49, 0x0a, 0x09, 0x70,
+     0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
+     0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f,
+     0x64, 0x75, 0x63, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+     0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x73, 0x12, 0x54,
+     0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+     0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x64,
+     0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x73, 0x74,
+     0x61, 0x74, 0x73, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+     0x12, 0x26, 0x0a, 0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6e,
+     0x74, 0x6f, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x0d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x6f,
+     0x46, 0x69, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70,
+     0x75, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x1d, 0x20, 0x01, 0x28,
+     0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x50, 0x61, 0x74,
+     0x68, 0x12, 0x2f, 0x0a, 0x14, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x77, 0x72,
+     0x69, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d,
+     0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x66, 0x69, 0x6c,
+     0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64,
+     0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x69,
+     0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65,
+     0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78,
+     0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65,
+     0x73, 0x12, 0x60, 0x0a, 0x13, 0x67, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61,
+     0x69, 0x6c, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73,
+     0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x2e, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76,
+     0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x52, 0x12, 0x67, 0x75, 0x61,
+     0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
+     0x64, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x65, 0x72,
+     0x72, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x0c, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65,
+     0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x6c,
+     0x75, 0x73, 0x68, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d,
+     0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x66, 0x6c, 0x75,
+     0x73, 0x68, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x28,
+     0x0a, 0x10, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+     0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0e, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x6f,
+     0x75, 0x74, 0x4d, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x64, 0x61, 0x74, 0x61,
+     0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x70,
+     0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18,
+     0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x17, 0x64, 0x61, 0x74, 0x61, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x69, 0x6d,
+     0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f,
+     0x74, 0x69, 0x66, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x75, 0x72,
+     0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69,
+     0x66, 0x79, 0x54, 0x72, 0x61, 0x63, 0x65, 0x75, 0x72, 0x12, 0x27, 0x0a,
+     0x0f, 0x62, 0x75, 0x67, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73,
+     0x63, 0x6f, 0x72, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
+     0x62, 0x75, 0x67, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x63, 0x6f,
+     0x72, 0x65, 0x12, 0x51, 0x0a, 0x0e, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65,
+     0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x11, 0x20, 0x01,
+     0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x72, 0x69,
+     0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d,
+     0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
+     0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x12,
+     0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
+     0x74, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x6d,
+     0x0a, 0x18, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61,
+     0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x2e, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
+     0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x52, 0x16, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
+     0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x75,
+     0x73, 0x65, 0x72, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x72,
+     0x61, 0x63, 0x69, 0x6e, 0x67, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x73, 0x65, 0x72, 0x42, 0x75,
+     0x69, 0x6c, 0x64, 0x54, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x12, 0x2e,
+     0x0a, 0x13, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x73,
+     0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x16, 0x20,
+     0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x53,
+     0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57,
+     0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f,
+     0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0e,
+     0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x72,
+     0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0f,
+     0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54,
+     0x79, 0x70, 0x65, 0x12, 0x67, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x69, 0x64,
+     0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x63,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x31, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x63, 0x69, 0x64, 0x65,
      0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f,
-     0x75, 0x75, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x62, 0x18, 0x1b, 0x20, 0x01,
+     0x69, 0x67, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x69, 0x64, 0x65, 0x6e, 0x74,
+     0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x75, 0x75,
+     0x69, 0x64, 0x5f, 0x6d, 0x73, 0x62, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x03,
+     0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65, 0x55, 0x75, 0x69, 0x64, 0x4d,
+     0x73, 0x62, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f,
+     0x75, 0x75, 0x69, 0x64, 0x5f, 0x6c, 0x73, 0x62, 0x18, 0x1c, 0x20, 0x01,
      0x28, 0x03, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65, 0x55, 0x75, 0x69,
-     0x64, 0x4d, 0x73, 0x62, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x63,
-     0x65, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x5f, 0x6c, 0x73, 0x62, 0x18, 0x1c,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65, 0x55,
-     0x75, 0x69, 0x64, 0x4c, 0x73, 0x62, 0x1a, 0xc7, 0x01, 0x0a, 0x0c, 0x42,
-     0x75, 0x66, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-     0x17, 0x0a, 0x07, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x7a, 0x65, 0x4b, 0x62,
-     0x12, 0x55, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c,
-     0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x43, 0x6f,
-     0x6e, 0x66, 0x69, 0x67, 0x2e, 0x46, 0x69, 0x6c, 0x6c, 0x50, 0x6f, 0x6c,
-     0x69, 0x63, 0x79, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x6c, 0x50, 0x6f, 0x6c,
-     0x69, 0x63, 0x79, 0x22, 0x3b, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x6c, 0x50,
-     0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53,
-     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f,
-     0x0a, 0x0b, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45,
-     0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x43, 0x41,
-     0x52, 0x44, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04,
-     0x08, 0x03, 0x10, 0x04, 0x1a, 0xb6, 0x01, 0x0a, 0x0a, 0x44, 0x61, 0x74,
-     0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63,
-     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f,
-     0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06,
-     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x72,
-     0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f,
-     0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
-     0x52, 0x12, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x4e, 0x61,
-     0x6d, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x1a,
-     0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x74,
-     0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72,
-     0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65,
-     0x67, 0x65, 0x78, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0xea, 0x02,
-     0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x44, 0x61, 0x74,
-     0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x64,
-     0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x63, 0x6b,
-     0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x74, 0x69, 0x6e,
-     0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x64, 0x69, 0x73,
-     0x61, 0x62, 0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x6e, 0x61,
-     0x70, 0x73, 0x68, 0x6f, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a,
-     0x14, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61,
-     0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
-     0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-     0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73,
-     0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
-     0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12,
-     0x34, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73,
-     0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
-     0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x73,
-     0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45,
-     0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x13, 0x70, 0x72, 0x69,
-     0x6d, 0x61, 0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63,
-     0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e,
-     0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6d, 0x61,
-     0x72, 0x79, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x63, 0x6b,
-     0x12, 0x30, 0x0a, 0x14, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74,
-     0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x6e, 0x61, 0x70,
-     0x73, 0x68, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
-     0x4d, 0x73, 0x1a, 0x77, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63,
-     0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d,
-     0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f,
-     0x64, 0x75, 0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
-     0x0b, 0x73, 0x68, 0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x68, 0x6d, 0x53,
-     0x69, 0x7a, 0x65, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x61, 0x67,
-     0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a,
-     0x65, 0x4b, 0x62, 0x1a, 0xe4, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74,
-     0x73, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e,
-     0x0a, 0x13, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67,
-     0x5f, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
-     0x69, 0x6e, 0x67, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x64, 0x12, 0x32,
-     0x0a, 0x15, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67,
-     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x69, 0x64, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x74, 0x72, 0x69, 0x67, 0x67,
-     0x65, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55,
-     0x69, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65,
-     0x72, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
-     0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x72,
-     0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66,
-     0x69, 0x67, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x74, 0x72, 0x69, 0x67,
-     0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65,
-     0x72, 0x69, 0x6e, 0x67, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x1a, 0x4c, 0x0a, 0x12, 0x47, 0x75,
-     0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72,
-     0x69, 0x64, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f,
-     0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64,
-     0x61, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61,
-     0x64, 0x50, 0x65, 0x72, 0x44, 0x61, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x1a, 0xa0, 0x03, 0x0a, 0x0d, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x0c, 0x74, 0x72,
-     0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54,
-     0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54,
-     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-     0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65,
-     0x52, 0x0b, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64,
-     0x65, 0x12, 0x4e, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
-     0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65,
+     0x64, 0x4c, 0x73, 0x62, 0x1a, 0xc7, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x66,
+     0x66, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a,
+     0x07, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x7a, 0x65, 0x4b, 0x62, 0x12, 0x55,
+     0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63,
+     0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x70, 0x65,
      0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
      0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e,
-     0x66, 0x69, 0x67, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52,
-     0x08, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a,
-     0x12, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d,
-     0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x10, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x69,
-     0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x1a, 0x71, 0x0a, 0x07, 0x54,
-     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
-     0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
-     0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63,
-     0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x65,
-     0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, 0x6f,
-     0x64, 0x75, 0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x67,
-     0x65, 0x78, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x64,
-     0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x79,
-     0x4d, 0x73, 0x22, 0x43, 0x0a, 0x0b, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65,
-     0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53,
-     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11,
-     0x0a, 0x0d, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x52, 0x41, 0x43,
-     0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x4f,
-     0x50, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x1a,
-     0x40, 0x0a, 0x16, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
-     0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-     0x67, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x70,
-     0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x50, 0x65, 0x72,
-     0x69, 0x6f, 0x64, 0x4d, 0x73, 0x1a, 0xbc, 0x01, 0x0a, 0x14, 0x49, 0x6e,
-     0x63, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
-     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65,
-     0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x64,
-     0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63,
-     0x6c, 0x61, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
-     0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43,
-     0x6c, 0x61, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x76,
-     0x61, 0x63, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79,
-     0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6b, 0x69,
-     0x70, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, 0x18, 0x04, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x44, 0x72, 0x6f,
-     0x70, 0x62, 0x6f, 0x78, 0x22, 0x55, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x6b,
-     0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65, 0x72,
-     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x4f, 0x43,
-     0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x55, 0x4e, 0x43, 0x48, 0x41, 0x4e,
-     0x47, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x43,
-     0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10,
-     0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x4f, 0x43, 0x4b, 0x44, 0x4f, 0x57,
-     0x4e, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x02, 0x22, 0x51, 0x0a, 0x0f, 0x43,
-     0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79,
-     0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45,
-     0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55,
-     0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
-     0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53,
-     0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46,
-     0x4c, 0x41, 0x54, 0x45, 0x10, 0x01, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10,
-     0x4a, 0x04, 0x08, 0x1a, 0x10, 0x1b, 0x2a, 0x8c, 0x02, 0x0a, 0x0c, 0x42,
-     0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12,
-     0x19, 0x0a, 0x15, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43,
-     0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
-     0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49,
-     0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x52, 0x45, 0x41, 0x4c,
-     0x54, 0x49, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x42, 0x55,
-     0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f,
-     0x52, 0x45, 0x41, 0x4c, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x43, 0x4f, 0x41,
-     0x52, 0x53, 0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x55, 0x49,
-     0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4d,
-     0x4f, 0x4e, 0x4f, 0x54, 0x4f, 0x4e, 0x49, 0x43, 0x10, 0x03, 0x12, 0x22,
-     0x0a, 0x1e, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c,
-     0x4f, 0x43, 0x4b, 0x5f, 0x4d, 0x4f, 0x4e, 0x4f, 0x54, 0x4f, 0x4e, 0x49,
-     0x43, 0x5f, 0x43, 0x4f, 0x41, 0x52, 0x53, 0x45, 0x10, 0x04, 0x12, 0x1f,
-     0x0a, 0x1b, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c,
-     0x4f, 0x43, 0x4b, 0x5f, 0x4d, 0x4f, 0x4e, 0x4f, 0x54, 0x4f, 0x4e, 0x49,
-     0x43, 0x5f, 0x52, 0x41, 0x57, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x42,
-     0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b,
-     0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x06, 0x12,
-     0x18, 0x0a, 0x14, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43,
-     0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4d, 0x41, 0x58, 0x5f, 0x49, 0x44, 0x10,
-     0x3f, 0x22, 0x04, 0x08, 0x07, 0x10, 0x07, 0x22, 0x04, 0x08, 0x08, 0x10,
-     0x08, 0x2a, 0x8e, 0x01, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x4c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49,
-     0x44, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12,
-     0x0d, 0x0a, 0x09, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x41, 0x44, 0x49, 0x4f,
-     0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49, 0x44, 0x5f, 0x45, 0x56,
-     0x45, 0x4e, 0x54, 0x53, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49,
-     0x44, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, 0x03, 0x12, 0x0d,
-     0x0a, 0x09, 0x4c, 0x49, 0x44, 0x5f, 0x43, 0x52, 0x41, 0x53, 0x48, 0x10,
-     0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x49, 0x44, 0x5f, 0x53, 0x54, 0x41,
-     0x54, 0x53, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x44, 0x5f,
-     0x53, 0x45, 0x43, 0x55, 0x52, 0x49, 0x54, 0x59, 0x10, 0x06, 0x12, 0x0e,
-     0x0a, 0x0a, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c,
-     0x10, 0x07, 0x2a, 0x9b, 0x01, 0x0a, 0x12, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x4c, 0x6f, 0x67, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
-     0x79, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x55, 0x4e,
+     0x67, 0x2e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x2e, 0x46, 0x69, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63,
+     0x79, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63,
+     0x79, 0x22, 0x3b, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x6c, 0x50, 0x6f, 0x6c,
+     0x69, 0x63, 0x79, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45,
+     0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b,
+     0x52, 0x49, 0x4e, 0x47, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x10,
+     0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x43, 0x41, 0x52, 0x44,
+     0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x03,
+     0x10, 0x04, 0x1a, 0xb6, 0x01, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x64,
+     0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x66, 0x69,
+     0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12,
+     0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65,
+     0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x1a, 0x70, 0x72,
+     0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f,
+     0x72, 0x65, 0x67, 0x65, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
+     0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x6f, 0x64,
+     0x75, 0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x67, 0x65,
+     0x78, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0xea, 0x02, 0x0a, 0x11,
+     0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x69, 0x73,
+     0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73,
+     0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x64, 0x69, 0x73, 0x61, 0x62,
+     0x6c, 0x65, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x6e, 0x61, 0x70, 0x73,
+     0x68, 0x6f, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x64,
+     0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72,
+     0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a,
+     0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73,
+     0x74, 0x65, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53,
+     0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x34, 0x0a,
+     0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x72,
+     0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18,
+     0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x73, 0x61, 0x62,
+     0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x76, 0x65,
+     0x6e, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x13, 0x70, 0x72, 0x69, 0x6d, 0x61,
+     0x72, 0x79, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f,
+     0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x43, 0x6c,
+     0x6f, 0x63, 0x6b, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
+     0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x30,
+     0x0a, 0x14, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69,
+     0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x06,
+     0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68,
+     0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73,
+     0x1a, 0x77, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72,
+     0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75,
+     0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x73,
+     0x68, 0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x02,
+     0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x68, 0x6d, 0x53, 0x69, 0x7a,
+     0x65, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x61, 0x67, 0x65, 0x5f,
+     0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x4b,
+     0x62, 0x1a, 0xe4, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x64,
+     0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x13,
+     0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x61,
+     0x6c, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x03, 0x52, 0x11, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e,
+     0x67, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15,
+     0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x63,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x05, 0x52, 0x13, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
+     0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x69, 0x64,
+     0x12, 0x30, 0x0a, 0x14, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69,
+     0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64,
+     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x72, 0x69, 0x67,
+     0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65,
+     0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x18, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69,
+     0x6e, 0x67, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+     0x6f, 0x6e, 0x49, 0x64, 0x1a, 0x4c, 0x0a, 0x12, 0x47, 0x75, 0x61, 0x72,
+     0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
+     0x65, 0x73, 0x12, 0x36, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70,
+     0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x79,
+     0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
+     0x52, 0x14, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x50,
+     0x65, 0x72, 0x44, 0x61, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0xa0,
+     0x03, 0x0a, 0x0d, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x0c, 0x74, 0x72, 0x69, 0x67,
+     0x67, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x0e, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x72, 0x69,
+     0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54,
+     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0b,
+     0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12,
+     0x4e, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18,
+     0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
+     0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x2e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x08, 0x74,
+     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x74,
+     0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
+     0x75, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
+     0x10, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65,
+     0x6f, 0x75, 0x74, 0x4d, 0x73, 0x1a, 0x71, 0x0a, 0x07, 0x54, 0x72, 0x69,
+     0x67, 0x67, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+     0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72,
+     0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18,
+     0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x64, 0x75,
+     0x63, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x67, 0x65, 0x78,
+     0x12, 0x22, 0x0a, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x64, 0x65, 0x6c,
+     0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
+     0x0b, 0x73, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73,
+     0x22, 0x43, 0x0a, 0x0b, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x4d,
+     0x6f, 0x64, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45,
+     0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d,
+     0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x49, 0x4e,
+     0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x4f, 0x50, 0x5f,
+     0x54, 0x52, 0x41, 0x43, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x1a, 0x40, 0x0a,
+     0x16, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c,
+     0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x70, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x50, 0x65, 0x72, 0x69, 0x6f,
+     0x64, 0x4d, 0x73, 0x1a, 0xbc, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x63, 0x69,
+     0x64, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74,
+     0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x63, 0x6b,
+     0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64,
+     0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61,
+     0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x73,
+     0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6c, 0x61,
+     0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65,
+     0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x61,
+     0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x69, 0x76, 0x61, 0x63,
+     0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
+     0x05, 0x52, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4c, 0x65,
+     0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x5f,
+     0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x44, 0x72, 0x6f, 0x70, 0x62,
+     0x6f, 0x78, 0x22, 0x55, 0x0a, 0x15, 0x4c, 0x6f, 0x63, 0x6b, 0x64, 0x6f,
+     0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74,
+     0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x4f, 0x43, 0x4b, 0x44,
+     0x4f, 0x57, 0x4e, 0x5f, 0x55, 0x4e, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45,
+     0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x43, 0x4b, 0x44,
+     0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x01, 0x12,
+     0x10, 0x0a, 0x0c, 0x4c, 0x4f, 0x43, 0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x5f,
+     0x53, 0x45, 0x54, 0x10, 0x02, 0x22, 0x51, 0x0a, 0x0f, 0x43, 0x6f, 0x6d,
+     0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65,
+     0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53,
+     0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53,
+     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c,
+     0x0a, 0x18, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f,
+     0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x4c, 0x41,
+     0x54, 0x45, 0x10, 0x01, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x4a, 0x04,
+     0x08, 0x1a, 0x10, 0x1b, 0x2a, 0x8c, 0x02, 0x0a, 0x0c, 0x42, 0x75, 0x69,
+     0x6c, 0x74, 0x69, 0x6e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x0a,
+     0x15, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f,
+     0x43, 0x4b, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
+     0x12, 0x1a, 0x0a, 0x16, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f,
+     0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x54, 0x49,
+     0x4d, 0x45, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x42, 0x55, 0x49, 0x4c,
+     0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x52, 0x45,
+     0x41, 0x4c, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x43, 0x4f, 0x41, 0x52, 0x53,
+     0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x55, 0x49, 0x4c, 0x54,
+     0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4d, 0x4f, 0x4e,
+     0x4f, 0x54, 0x4f, 0x4e, 0x49, 0x43, 0x10, 0x03, 0x12, 0x22, 0x0a, 0x1e,
+     0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43,
+     0x4b, 0x5f, 0x4d, 0x4f, 0x4e, 0x4f, 0x54, 0x4f, 0x4e, 0x49, 0x43, 0x5f,
+     0x43, 0x4f, 0x41, 0x52, 0x53, 0x45, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b,
+     0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43,
+     0x4b, 0x5f, 0x4d, 0x4f, 0x4e, 0x4f, 0x54, 0x4f, 0x4e, 0x49, 0x43, 0x5f,
+     0x52, 0x41, 0x57, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x55, 0x49,
+     0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x42,
+     0x4f, 0x4f, 0x54, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x06, 0x12, 0x18, 0x0a,
+     0x14, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x4f,
+     0x43, 0x4b, 0x5f, 0x4d, 0x41, 0x58, 0x5f, 0x49, 0x44, 0x10, 0x3f, 0x22,
+     0x04, 0x08, 0x07, 0x10, 0x07, 0x22, 0x04, 0x08, 0x08, 0x10, 0x08, 0x2a,
+     0x8e, 0x01, 0x0a, 0x0c, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c,
+     0x6f, 0x67, 0x49, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4c, 0x49, 0x44, 0x5f,
+     0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a,
+     0x09, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x41, 0x44, 0x49, 0x4f, 0x10, 0x01,
+     0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49, 0x44, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+     0x54, 0x53, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x4c, 0x49, 0x44, 0x5f,
+     0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09,
+     0x4c, 0x49, 0x44, 0x5f, 0x43, 0x52, 0x41, 0x53, 0x48, 0x10, 0x04, 0x12,
+     0x0d, 0x0a, 0x09, 0x4c, 0x49, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x53,
+     0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x44, 0x5f, 0x53, 0x45,
+     0x43, 0x55, 0x52, 0x49, 0x54, 0x59, 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a,
+     0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x10, 0x07,
+     0x2a, 0x9b, 0x01, 0x0a, 0x12, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+     0x4c, 0x6f, 0x67, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12,
+     0x14, 0x0a, 0x10, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+     0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a,
+     0x0b, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44,
+     0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x56,
+     0x45, 0x52, 0x42, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a,
+     0x50, 0x52, 0x49, 0x4f, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x03,
+     0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x49, 0x4e, 0x46,
+     0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x49, 0x4f, 0x5f,
+     0x57, 0x41, 0x52, 0x4e, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52,
+     0x49, 0x4f, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x06, 0x12, 0x0e,
+     0x0a, 0x0a, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c,
+     0x10, 0x07, 0x2a, 0xbf, 0x06, 0x0a, 0x0f, 0x4d, 0x65, 0x6d, 0x69, 0x6e,
+     0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x17,
+     0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x55, 0x4e,
      0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
-     0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x55, 0x4e, 0x55, 0x53,
-     0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x49, 0x4f,
-     0x5f, 0x56, 0x45, 0x52, 0x42, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, 0x0e,
-     0x0a, 0x0a, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47,
-     0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x49,
-     0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x49,
-     0x4f, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a,
-     0x50, 0x52, 0x49, 0x4f, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x06,
-     0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x49, 0x4f, 0x5f, 0x46, 0x41, 0x54,
-     0x41, 0x4c, 0x10, 0x07, 0x2a, 0xbf, 0x06, 0x0a, 0x0f, 0x4d, 0x65, 0x6d,
-     0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
-     0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
-     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
-     0x00, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x4d, 0x45, 0x4d, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x01,
-     0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
-     0x4d, 0x45, 0x4d, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x10, 0x02, 0x12, 0x19,
-     0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x45,
-     0x4d, 0x5f, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x10,
-     0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x53, 0x10, 0x04, 0x12, 0x12,
-     0x0a, 0x0e, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x41,
-     0x43, 0x48, 0x45, 0x44, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45,
-     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x43,
-     0x41, 0x43, 0x48, 0x45, 0x44, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x4d,
-     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56,
-     0x45, 0x10, 0x07, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
-     0x46, 0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10,
-     0x08, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e,
-     0x10, 0x09, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46,
-     0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41,
-     0x4e, 0x4f, 0x4e, 0x10, 0x0a, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d,
-     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f,
-     0x46, 0x49, 0x4c, 0x45, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45,
-     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49,
-     0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x0c, 0x12, 0x17, 0x0a,
-     0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x55, 0x4e, 0x45,
-     0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0d, 0x12, 0x13,
-     0x0a, 0x0f, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x4c,
-     0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x16, 0x0a, 0x12, 0x4d,
-     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f,
-     0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x0f, 0x12, 0x15, 0x0a, 0x11, 0x4d,
-     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f,
-     0x46, 0x52, 0x45, 0x45, 0x10, 0x10, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45,
-     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x10,
-     0x11, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x12,
-     0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
-     0x41, 0x4e, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x13,
-     0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
-     0x4d, 0x41, 0x50, 0x50, 0x45, 0x44, 0x10, 0x14, 0x12, 0x11, 0x0a, 0x0d,
-     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x48, 0x4d, 0x45,
-     0x4d, 0x10, 0x15, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
-     0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x10, 0x16, 0x12, 0x1c, 0x0a,
-     0x18, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41,
-     0x42, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c,
-     0x45, 0x10, 0x17, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
-     0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x55, 0x4e, 0x52, 0x45,
-     0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x18, 0x12,
-     0x18, 0x0a, 0x14, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4b,
-     0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10,
-     0x19, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53,
-     0x10, 0x1a, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46,
-     0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x4c, 0x49, 0x4d,
-     0x49, 0x54, 0x10, 0x1b, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49,
-     0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x45, 0x44,
-     0x5f, 0x41, 0x53, 0x10, 0x1c, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d,
-     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c, 0x4c, 0x4f, 0x43,
-     0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x1d, 0x12, 0x18, 0x0a, 0x14,
-     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c,
-     0x4c, 0x4f, 0x43, 0x5f, 0x55, 0x53, 0x45, 0x44, 0x10, 0x1e, 0x12, 0x19,
-     0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d,
-     0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x10,
-     0x1f, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
-     0x5f, 0x43, 0x4d, 0x41, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x20,
-     0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
-     0x43, 0x4d, 0x41, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x10, 0x21, 0x2a, 0xc6,
-     0x15, 0x0a, 0x0e, 0x56, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-     0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f,
-     0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x56,
-     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4c, 0x4c,
-     0x4f, 0x43, 0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x12, 0x1b,
-     0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
+     0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d,
+     0x45, 0x4d, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14,
+     0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x45,
+     0x4d, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x5f,
+     0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12,
+     0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x42,
+     0x55, 0x46, 0x46, 0x45, 0x52, 0x53, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x41, 0x43, 0x48,
+     0x45, 0x44, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49,
+     0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x43, 0x41, 0x43,
+     0x48, 0x45, 0x44, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10,
+     0x07, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
+     0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x08, 0x12,
+     0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x09,
+     0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
      0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f,
-     0x4e, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f,
-     0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x4e, 0x41, 0x43,
-     0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x05, 0x12,
-     0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
-     0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45,
-     0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41,
-     0x42, 0x4c, 0x45, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4d, 0x4c, 0x4f, 0x43, 0x4b,
-     0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x47,
-     0x45, 0x53, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4d, 0x41, 0x50, 0x50, 0x45, 0x44,
-     0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x47,
-     0x45, 0x53, 0x10, 0x0b, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x10,
-     0x0c, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43, 0x4b,
-     0x10, 0x0d, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x52, 0x45, 0x43,
-     0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0e, 0x12, 0x20,
-     0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
-     0x53, 0x4c, 0x41, 0x42, 0x5f, 0x55, 0x4e, 0x52, 0x45, 0x43, 0x4c, 0x41,
-     0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0f, 0x12, 0x1e, 0x0a, 0x1a,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x50, 0x41,
-     0x47, 0x45, 0x5f, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x47,
-     0x45, 0x53, 0x10, 0x10, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c,
-     0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4f, 0x56,
-     0x45, 0x52, 0x48, 0x45, 0x41, 0x44, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x55, 0x4e,
-     0x53, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x13, 0x12, 0x14, 0x0a, 0x10,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x42, 0x4f,
-     0x55, 0x4e, 0x43, 0x45, 0x10, 0x14, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x56, 0x4d, 0x53, 0x43,
-     0x41, 0x4e, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x15, 0x12, 0x26,
-     0x0a, 0x22, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
-     0x56, 0x4d, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x44,
-     0x49, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d,
-     0x10, 0x16, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43,
-     0x4b, 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x53,
-     0x4f, 0x4c, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10,
-     0x18, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x4e, 0x52, 0x5f, 0x49, 0x53, 0x4f, 0x4c, 0x41, 0x54, 0x45, 0x44, 0x5f,
-     0x46, 0x49, 0x4c, 0x45, 0x10, 0x19, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x48, 0x4d, 0x45,
-     0x4d, 0x10, 0x1a, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x49, 0x45, 0x44,
-     0x10, 0x1b, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x54, 0x45, 0x4e, 0x10,
-     0x1c, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x4e, 0x52, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x5f, 0x53, 0x43, 0x41,
-     0x4e, 0x4e, 0x45, 0x44, 0x10, 0x1d, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47,
-     0x53, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10,
-     0x1e, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f, 0x41,
-     0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x1f, 0x12, 0x21, 0x0a,
-     0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b,
-     0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x52,
-     0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4e,
-     0x4f, 0x4e, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x41, 0x52, 0x45,
-     0x4e, 0x54, 0x5f, 0x48, 0x55, 0x47, 0x45, 0x50, 0x41, 0x47, 0x45, 0x53,
-     0x10, 0x21, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x43, 0x4d, 0x41,
-     0x10, 0x22, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x43, 0x41, 0x43, 0x48,
-     0x45, 0x10, 0x23, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x54,
-     0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x10, 0x24, 0x12, 0x28,
-     0x0a, 0x24, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
-     0x44, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x47, 0x52,
-     0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f,
-     0x4c, 0x44, 0x10, 0x25, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47, 0x49, 0x4e, 0x10, 0x26, 0x12,
-     0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
-     0x50, 0x47, 0x4f, 0x55, 0x54, 0x10, 0x27, 0x12, 0x17, 0x0a, 0x13, 0x56,
-     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47, 0x4f, 0x55,
-     0x54, 0x43, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x28, 0x12, 0x11, 0x0a, 0x0d,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x53, 0x57, 0x50, 0x49,
-     0x4e, 0x10, 0x29, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x50, 0x53, 0x57, 0x50, 0x4f, 0x55, 0x54, 0x10, 0x2a, 0x12,
-     0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
-     0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x2b, 0x12,
-     0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
-     0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c,
-     0x10, 0x2c, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x50, 0x47, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x4d, 0x4f, 0x56,
-     0x41, 0x42, 0x4c, 0x45, 0x10, 0x2d, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x46, 0x52, 0x45, 0x45, 0x10,
-     0x2e, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x50, 0x47, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x2f,
-     0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
-     0x47, 0x44, 0x45, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10,
-     0x30, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x50, 0x47, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x31, 0x12, 0x15, 0x0a,
-     0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x4d, 0x41,
-     0x4a, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x32, 0x12, 0x17, 0x0a, 0x13,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x45, 0x46,
-     0x49, 0x4c, 0x4c, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x33, 0x12, 0x1a, 0x0a,
-     0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x45,
-     0x46, 0x49, 0x4c, 0x4c, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10,
-     0x34, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c, 0x4c, 0x5f, 0x4d, 0x4f, 0x56,
-     0x41, 0x42, 0x4c, 0x45, 0x10, 0x35, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c,
-     0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x44, 0x4d, 0x41, 0x10,
-     0x36, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
-     0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41,
-     0x50, 0x44, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x37, 0x12,
-     0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
-     0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44,
-     0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x38, 0x12, 0x1d,
-     0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53,
-     0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f,
-     0x44, 0x4d, 0x41, 0x10, 0x39, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f,
-     0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41,
-     0x4c, 0x10, 0x3a, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49,
-     0x52, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45,
-     0x10, 0x3b, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41,
-     0x50, 0x44, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x3c, 0x12, 0x1f, 0x0a, 0x1b,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41,
-     0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4e, 0x4f, 0x52,
-     0x4d, 0x41, 0x4c, 0x10, 0x3d, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b,
-     0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c,
-     0x45, 0x10, 0x3e, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52,
-     0x45, 0x43, 0x54, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x3f, 0x12, 0x1f, 0x0a,
-     0x1b, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43,
-     0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4e, 0x4f,
-     0x52, 0x4d, 0x41, 0x4c, 0x10, 0x40, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f,
-     0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42,
-     0x4c, 0x45, 0x10, 0x41, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49,
-     0x52, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x48, 0x52, 0x4f, 0x54, 0x54, 0x4c,
-     0x45, 0x10, 0x42, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41,
-     0x54, 0x5f, 0x50, 0x47, 0x49, 0x4e, 0x4f, 0x44, 0x45, 0x53, 0x54, 0x45,
-     0x41, 0x4c, 0x10, 0x43, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54,
-     0x41, 0x54, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x53, 0x5f, 0x53, 0x43, 0x41,
-     0x4e, 0x4e, 0x45, 0x44, 0x10, 0x44, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d,
-     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f,
-     0x49, 0x4e, 0x4f, 0x44, 0x45, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x10, 0x45,
-     0x12, 0x27, 0x0a, 0x23, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4b,
-     0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4c, 0x4f, 0x57, 0x5f, 0x57, 0x4d,
-     0x41, 0x52, 0x4b, 0x5f, 0x48, 0x49, 0x54, 0x5f, 0x51, 0x55, 0x49, 0x43,
-     0x4b, 0x4c, 0x59, 0x10, 0x46, 0x12, 0x28, 0x0a, 0x24, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x48,
-     0x49, 0x47, 0x48, 0x5f, 0x57, 0x4d, 0x41, 0x52, 0x4b, 0x5f, 0x48, 0x49,
-     0x54, 0x5f, 0x51, 0x55, 0x49, 0x43, 0x4b, 0x4c, 0x59, 0x10, 0x47, 0x12,
-     0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x41,
-     0x47, 0x45, 0x4f, 0x55, 0x54, 0x52, 0x55, 0x4e, 0x10, 0x48, 0x12, 0x15,
-     0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x41, 0x4c, 0x4c,
-     0x4f, 0x43, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x10, 0x49, 0x12, 0x14, 0x0a,
-     0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x4f,
-     0x54, 0x41, 0x54, 0x45, 0x44, 0x10, 0x4a, 0x12, 0x19, 0x0a, 0x15, 0x56,
-     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x50,
-     0x41, 0x47, 0x45, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10, 0x4b, 0x12, 0x14,
-     0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x44, 0x52, 0x4f,
-     0x50, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x10, 0x4c, 0x12, 0x1c, 0x0a, 0x18,
-     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x4d, 0x49, 0x47,
-     0x52, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53,
-     0x10, 0x4d, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x50, 0x47, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x46,
-     0x41, 0x49, 0x4c, 0x10, 0x4e, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f,
-     0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e,
-     0x4e, 0x45, 0x44, 0x10, 0x4f, 0x12, 0x1f, 0x0a, 0x1b, 0x56, 0x4d, 0x53,
-     0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f,
-     0x46, 0x52, 0x45, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44,
-     0x10, 0x50, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x49, 0x53, 0x4f,
-     0x4c, 0x41, 0x54, 0x45, 0x44, 0x10, 0x51, 0x12, 0x18, 0x0a, 0x14, 0x56,
-     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43,
-     0x54, 0x5f, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x10, 0x52, 0x12, 0x17, 0x0a,
-     0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50,
-     0x41, 0x43, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x53, 0x12, 0x1a,
-     0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d,
-     0x50, 0x41, 0x43, 0x54, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53,
-     0x10, 0x54, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
-     0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x44, 0x41, 0x45,
-     0x4d, 0x4f, 0x4e, 0x5f, 0x57, 0x41, 0x4b, 0x45, 0x10, 0x55, 0x12, 0x21,
-     0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45,
-     0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53,
-     0x5f, 0x43, 0x55, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x56, 0x12, 0x22, 0x0a,
-     0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
-     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
-     0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x57, 0x12, 0x22, 0x0a,
-     0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
-     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
-     0x52, 0x45, 0x53, 0x43, 0x55, 0x45, 0x44, 0x10, 0x58, 0x12, 0x22, 0x0a,
-     0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
-     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
-     0x4d, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x59, 0x12, 0x24, 0x0a,
-     0x20, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
-     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
-     0x4d, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x5a, 0x12,
-     0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e,
-     0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47,
-     0x53, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x45, 0x44, 0x10, 0x5b, 0x12,
-     0x23, 0x0a, 0x1f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e,
-     0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47,
-     0x53, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x5c,
+     0x4e, 0x10, 0x0a, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49,
+     0x4c, 0x45, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49,
+     0x4e, 0x46, 0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45,
+     0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x0c, 0x12, 0x17, 0x0a, 0x13, 0x4d,
+     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49,
+     0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0d, 0x12, 0x13, 0x0a, 0x0f,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x4c, 0x4f, 0x43,
+     0x4b, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x54, 0x4f,
+     0x54, 0x41, 0x4c, 0x10, 0x0f, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x46, 0x52,
+     0x45, 0x45, 0x10, 0x10, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x4d, 0x49,
+     0x4e, 0x46, 0x4f, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x10, 0x11, 0x12,
+     0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x57,
+     0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x12, 0x12, 0x16,
+     0x0a, 0x12, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x4e,
+     0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x13, 0x12, 0x12,
+     0x0a, 0x0e, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x41,
+     0x50, 0x50, 0x45, 0x44, 0x10, 0x14, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x48, 0x4d, 0x45, 0x4d, 0x10,
+     0x15, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
+     0x5f, 0x53, 0x4c, 0x41, 0x42, 0x10, 0x16, 0x12, 0x1c, 0x0a, 0x18, 0x4d,
+     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f,
+     0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10,
+     0x17, 0x12, 0x1e, 0x0a, 0x1a, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
+     0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x55, 0x4e, 0x52, 0x45, 0x43, 0x4c,
+     0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x18, 0x12, 0x18, 0x0a,
+     0x14, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4b, 0x45, 0x52,
+     0x4e, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10, 0x19, 0x12,
+     0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x50,
+     0x41, 0x47, 0x45, 0x5f, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, 0x1a,
+     0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
+     0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54,
+     0x10, 0x1b, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46,
+     0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x5f, 0x41,
+     0x53, 0x10, 0x1c, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x54,
+     0x4f, 0x54, 0x41, 0x4c, 0x10, 0x1d, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c, 0x4c, 0x4f,
+     0x43, 0x5f, 0x55, 0x53, 0x45, 0x44, 0x10, 0x1e, 0x12, 0x19, 0x0a, 0x15,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c,
+     0x4c, 0x4f, 0x43, 0x5f, 0x43, 0x48, 0x55, 0x4e, 0x4b, 0x10, 0x1f, 0x12,
+     0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43,
+     0x4d, 0x41, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x20, 0x12, 0x14,
+     0x0a, 0x10, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4d,
+     0x41, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x10, 0x21, 0x2a, 0x9b, 0x1d, 0x0a,
+     0x0e, 0x56, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+     0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
+     0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x41,
+     0x47, 0x45, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x43,
+     0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x4e,
+     0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10,
+     0x03, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e,
+     0x4f, 0x4e, 0x10, 0x04, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49,
+     0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x05, 0x12, 0x19, 0x0a,
+     0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x06,
+     0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c,
+     0x45, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4d, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x08,
+     0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53,
+     0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x4e, 0x52, 0x5f, 0x4d, 0x41, 0x50, 0x50, 0x45, 0x44, 0x10, 0x0a,
+     0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53,
+     0x10, 0x0b, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x10, 0x0c, 0x12,
+     0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x0d,
+     0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41,
+     0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0e, 0x12, 0x20, 0x0a, 0x1c,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x4c,
+     0x41, 0x42, 0x5f, 0x55, 0x4e, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d,
+     0x41, 0x42, 0x4c, 0x45, 0x10, 0x0f, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x50, 0x41, 0x47, 0x45,
+     0x5f, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53,
+     0x10, 0x10, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x4e, 0x52, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f, 0x53,
+     0x54, 0x41, 0x43, 0x4b, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4f, 0x56, 0x45, 0x52,
+     0x48, 0x45, 0x41, 0x44, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x54,
+     0x41, 0x42, 0x4c, 0x45, 0x10, 0x13, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x42, 0x4f, 0x55, 0x4e,
+     0x43, 0x45, 0x10, 0x14, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x56, 0x4d, 0x53, 0x43, 0x41, 0x4e,
+     0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x15, 0x12, 0x26, 0x0a, 0x22,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x56, 0x4d,
+     0x53, 0x43, 0x41, 0x4e, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x44, 0x49, 0x41,
+     0x54, 0x45, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x16,
+     0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42, 0x41, 0x43, 0x4b, 0x5f,
+     0x54, 0x45, 0x4d, 0x50, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x53, 0x4f, 0x4c,
+     0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x18, 0x12,
+     0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x49, 0x53, 0x4f, 0x4c, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49,
+     0x4c, 0x45, 0x10, 0x19, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x48, 0x4d, 0x45, 0x4d, 0x10,
+     0x1a, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x49, 0x45, 0x44, 0x10, 0x1b,
      0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
-     0x52, 0x5f, 0x5a, 0x53, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x5d, 0x12,
-     0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
-     0x5f, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x45, 0x41, 0x50, 0x10, 0x5e, 0x12,
-     0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
-     0x5f, 0x47, 0x50, 0x55, 0x5f, 0x48, 0x45, 0x41, 0x50, 0x10, 0x5f}};
+     0x52, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x54, 0x45, 0x4e, 0x10, 0x1c, 0x12,
+     0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e,
+     0x45, 0x44, 0x10, 0x1d, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45,
+     0x54, 0x5f, 0x52, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x1e, 0x12,
+     0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f,
+     0x52, 0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f, 0x41, 0x43, 0x54,
+     0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x1f, 0x12, 0x21, 0x0a, 0x1d, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e,
+     0x47, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x52, 0x45, 0x43,
+     0x4c, 0x41, 0x49, 0x4d, 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4e, 0x4f, 0x4e,
+     0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54,
+     0x5f, 0x48, 0x55, 0x47, 0x45, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x21,
+     0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x43, 0x4d, 0x41, 0x10, 0x22,
+     0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10,
+     0x23, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x54, 0x48, 0x52,
+     0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x10, 0x24, 0x12, 0x28, 0x0a, 0x24,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49,
+     0x52, 0x54, 0x59, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x47, 0x52, 0x4f, 0x55,
+     0x4e, 0x44, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44,
+     0x10, 0x25, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x50, 0x47, 0x49, 0x4e, 0x10, 0x26, 0x12, 0x12, 0x0a,
+     0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47,
+     0x4f, 0x55, 0x54, 0x10, 0x27, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47, 0x4f, 0x55, 0x54, 0x43,
+     0x4c, 0x45, 0x41, 0x4e, 0x10, 0x28, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x53, 0x57, 0x50, 0x49, 0x4e, 0x10,
+     0x29, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x53, 0x57, 0x50, 0x4f, 0x55, 0x54, 0x10, 0x2a, 0x12, 0x16, 0x0a,
+     0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x4c,
+     0x4c, 0x4f, 0x43, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x2b, 0x12, 0x19, 0x0a,
+     0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x4c,
+     0x4c, 0x4f, 0x43, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x2c,
+     0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
+     0x47, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42,
+     0x4c, 0x45, 0x10, 0x2d, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x46, 0x52, 0x45, 0x45, 0x10, 0x2e, 0x12,
+     0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x2f, 0x12, 0x17,
+     0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x44,
+     0x45, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x30, 0x12,
+     0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x31, 0x12, 0x15, 0x0a, 0x11, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x4d, 0x41, 0x4a, 0x46,
+     0x41, 0x55, 0x4c, 0x54, 0x10, 0x32, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c,
+     0x4c, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x33, 0x12, 0x1a, 0x0a, 0x16, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x45, 0x46, 0x49,
+     0x4c, 0x4c, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x34, 0x12,
+     0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x52, 0x45, 0x46, 0x49, 0x4c, 0x4c, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42,
+     0x4c, 0x45, 0x10, 0x35, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b,
+     0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x36, 0x12,
+     0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44,
+     0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x37, 0x12, 0x21, 0x0a,
+     0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54,
+     0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4d,
+     0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x38, 0x12, 0x1d, 0x0a, 0x19,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45,
+     0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x44, 0x4d,
+     0x41, 0x10, 0x39, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49,
+     0x52, 0x45, 0x43, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10,
+     0x3a, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45,
+     0x43, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x3b,
+     0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
+     0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44,
+     0x5f, 0x44, 0x4d, 0x41, 0x10, 0x3c, 0x12, 0x1f, 0x0a, 0x1b, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f,
+     0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41,
+     0x4c, 0x10, 0x3d, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57,
+     0x41, 0x50, 0x44, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10,
+     0x3e, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43,
+     0x54, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x3f, 0x12, 0x1f, 0x0a, 0x1b, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e,
+     0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d,
+     0x41, 0x4c, 0x10, 0x40, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49,
+     0x52, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45,
+     0x10, 0x41, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45,
+     0x43, 0x54, 0x5f, 0x54, 0x48, 0x52, 0x4f, 0x54, 0x54, 0x4c, 0x45, 0x10,
+     0x42, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x49, 0x4e, 0x4f, 0x44, 0x45, 0x53, 0x54, 0x45, 0x41, 0x4c,
+     0x10, 0x43, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x53, 0x4c, 0x41, 0x42, 0x53, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e,
+     0x45, 0x44, 0x10, 0x44, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x49, 0x4e,
+     0x4f, 0x44, 0x45, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x10, 0x45, 0x12, 0x27,
+     0x0a, 0x23, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57,
+     0x41, 0x50, 0x44, 0x5f, 0x4c, 0x4f, 0x57, 0x5f, 0x57, 0x4d, 0x41, 0x52,
+     0x4b, 0x5f, 0x48, 0x49, 0x54, 0x5f, 0x51, 0x55, 0x49, 0x43, 0x4b, 0x4c,
+     0x59, 0x10, 0x46, 0x12, 0x28, 0x0a, 0x24, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x48, 0x49, 0x47,
+     0x48, 0x5f, 0x57, 0x4d, 0x41, 0x52, 0x4b, 0x5f, 0x48, 0x49, 0x54, 0x5f,
+     0x51, 0x55, 0x49, 0x43, 0x4b, 0x4c, 0x59, 0x10, 0x47, 0x12, 0x15, 0x0a,
+     0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x41, 0x47, 0x45,
+     0x4f, 0x55, 0x54, 0x52, 0x55, 0x4e, 0x10, 0x48, 0x12, 0x15, 0x0a, 0x11,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x43,
+     0x53, 0x54, 0x41, 0x4c, 0x4c, 0x10, 0x49, 0x12, 0x14, 0x0a, 0x10, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x4f, 0x54, 0x41,
+     0x54, 0x45, 0x44, 0x10, 0x4a, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x50, 0x41, 0x47,
+     0x45, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10, 0x4b, 0x12, 0x14, 0x0a, 0x10,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x5f,
+     0x53, 0x4c, 0x41, 0x42, 0x10, 0x4c, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x4d, 0x49, 0x47, 0x52, 0x41,
+     0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x4d,
+     0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
+     0x47, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49,
+     0x4c, 0x10, 0x4e, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x4d, 0x49,
+     0x47, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45,
+     0x44, 0x10, 0x4f, 0x12, 0x1f, 0x0a, 0x1b, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x46, 0x52,
+     0x45, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x50,
+     0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43,
+     0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x49, 0x53, 0x4f, 0x4c, 0x41,
+     0x54, 0x45, 0x44, 0x10, 0x51, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f,
+     0x53, 0x54, 0x41, 0x4c, 0x4c, 0x10, 0x52, 0x12, 0x17, 0x0a, 0x13, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43,
+     0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x53, 0x12, 0x1a, 0x0a, 0x16,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41,
+     0x43, 0x54, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x54,
+     0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43,
+     0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f,
+     0x4e, 0x5f, 0x57, 0x41, 0x4b, 0x45, 0x10, 0x55, 0x12, 0x21, 0x0a, 0x1d,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49,
+     0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x43,
+     0x55, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x56, 0x12, 0x22, 0x0a, 0x1e, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43,
+     0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x53, 0x43,
+     0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x57, 0x12, 0x22, 0x0a, 0x1e, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43,
+     0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x52, 0x45,
+     0x53, 0x43, 0x55, 0x45, 0x44, 0x10, 0x58, 0x12, 0x22, 0x0a, 0x1e, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43,
+     0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x4d, 0x4c,
+     0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x59, 0x12, 0x24, 0x0a, 0x20, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43,
+     0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x4d, 0x55,
+     0x4e, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x5a, 0x12, 0x22, 0x0a,
+     0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
+     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
+     0x43, 0x4c, 0x45, 0x41, 0x52, 0x45, 0x44, 0x10, 0x5b, 0x12, 0x23, 0x0a,
+     0x1f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56,
+     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f,
+     0x53, 0x54, 0x52, 0x41, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x5c, 0x12, 0x15,
+     0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
+     0x5a, 0x53, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x5d, 0x12, 0x16, 0x0a,
+     0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49,
+     0x4f, 0x4e, 0x5f, 0x48, 0x45, 0x41, 0x50, 0x10, 0x5e, 0x12, 0x16, 0x0a,
+     0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x47,
+     0x50, 0x55, 0x5f, 0x48, 0x45, 0x41, 0x50, 0x10, 0x5f, 0x12, 0x19, 0x0a,
+     0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f,
+     0x43, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x60,
+     0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x41,
+     0x4c, 0x4c, 0x4f, 0x43, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x5f, 0x4d, 0x4f,
+     0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x61, 0x12, 0x1c, 0x0a, 0x18, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x53,
+     0x54, 0x41, 0x4c, 0x4c, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10,
+     0x62, 0x12, 0x26, 0x0a, 0x22, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x44, 0x41, 0x45, 0x4d,
+     0x4f, 0x4e, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e,
+     0x4e, 0x45, 0x44, 0x10, 0x63, 0x12, 0x29, 0x0a, 0x25, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f,
+     0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41,
+     0x54, 0x45, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x64,
+     0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x46, 0x41, 0x53, 0x54, 0x52, 0x50, 0x43, 0x10, 0x65, 0x12,
+     0x24, 0x0a, 0x20, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x49, 0x4e, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4c, 0x59, 0x5f,
+     0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10,
+     0x66, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x45, 0x41, 0x50, 0x5f,
+     0x50, 0x4f, 0x4f, 0x4c, 0x10, 0x67, 0x12, 0x25, 0x0a, 0x21, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4b, 0x45, 0x52, 0x4e,
+     0x45, 0x4c, 0x5f, 0x4d, 0x49, 0x53, 0x43, 0x5f, 0x52, 0x45, 0x43, 0x4c,
+     0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x68, 0x12, 0x25, 0x0a,
+     0x21, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53,
+     0x48, 0x41, 0x44, 0x4f, 0x57, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x53,
+     0x54, 0x41, 0x43, 0x4b, 0x5f, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x69,
+     0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x53, 0x48, 0x4d, 0x45, 0x4d, 0x5f, 0x48, 0x55, 0x47, 0x45,
+     0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x6a, 0x12, 0x1d, 0x0a, 0x19, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x48, 0x4d,
+     0x45, 0x4d, 0x5f, 0x50, 0x4d, 0x44, 0x4d, 0x41, 0x50, 0x50, 0x45, 0x44,
+     0x10, 0x6b, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x4e, 0x52, 0x5f, 0x55, 0x4e, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49,
+     0x4d, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10,
+     0x6c, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49,
+     0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x6d, 0x12, 0x1e, 0x0a,
+     0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x5a,
+     0x4f, 0x4e, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46,
+     0x49, 0x4c, 0x45, 0x10, 0x6e, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f,
+     0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f,
+     0x4e, 0x10, 0x6f, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f, 0x49, 0x4e,
+     0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10,
+     0x70, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f, 0x55, 0x4e, 0x45, 0x56,
+     0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x71, 0x12, 0x20, 0x0a,
+     0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x5a,
+     0x4f, 0x4e, 0x45, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x50, 0x45,
+     0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x72, 0x12, 0x13, 0x0a, 0x0f, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4f, 0x4f, 0x4d, 0x5f, 0x4b, 0x49,
+     0x4c, 0x4c, 0x10, 0x73, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x4c, 0x41, 0x5a, 0x59, 0x46, 0x52, 0x45,
+     0x45, 0x10, 0x74, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x4c, 0x41, 0x5a, 0x59, 0x46, 0x52, 0x45, 0x45,
+     0x44, 0x10, 0x75, 0x12, 0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c, 0x4c, 0x10, 0x76,
+     0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
+     0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54,
+     0x10, 0x77, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41,
+     0x50, 0x44, 0x10, 0x78, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x4b, 0x49, 0x50, 0x5f, 0x44, 0x4d,
+     0x41, 0x10, 0x79, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x53, 0x4b, 0x49, 0x50, 0x5f, 0x4d, 0x4f, 0x56,
+     0x41, 0x42, 0x4c, 0x45, 0x10, 0x7a, 0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x4b, 0x49, 0x50, 0x5f,
+     0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x7b, 0x12, 0x19, 0x0a, 0x15,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45,
+     0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x10, 0x7c, 0x12,
+     0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44,
+     0x10, 0x7d, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x52, 0x41, 0x10, 0x7e, 0x12, 0x16,
+     0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x53, 0x57, 0x41,
+     0x50, 0x5f, 0x52, 0x41, 0x5f, 0x48, 0x49, 0x54, 0x10, 0x7f, 0x12, 0x1e,
+     0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52,
+     0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x53, 0x54,
+     0x4f, 0x52, 0x45, 0x10, 0x80, 0x01}};
 
 }  // namespace perfetto
 
diff --git a/src/perfetto_cmd/rate_limiter.cc b/src/perfetto_cmd/rate_limiter.cc
index daa4403..f09d173 100644
--- a/src/perfetto_cmd/rate_limiter.cc
+++ b/src/perfetto_cmd/rate_limiter.cc
@@ -48,13 +48,13 @@
 RateLimiter::RateLimiter() = default;
 RateLimiter::~RateLimiter() = default;
 
-bool RateLimiter::ShouldTrace(const Args& args) {
+RateLimiter::ShouldTraceResponse RateLimiter::ShouldTrace(const Args& args) {
   uint64_t now_in_s = static_cast<uint64_t>(args.current_time.count());
 
-  // Not storing in Dropbox?
+  // Not uploading?
   // -> We can just trace.
-  if (!args.is_dropbox)
-    return true;
+  if (!args.is_uploading)
+    return ShouldTraceResponse::kOkToTrace;
 
   // If we're tracing a user build we should only trace if the override in
   // the config is set:
@@ -62,7 +62,7 @@
     PERFETTO_ELOG(
         "Guardrail: allow_user_build_tracing must be set to trace on user "
         "builds");
-    return false;
+    return ShouldTraceResponse::kNotAllowedOnUserBuild;
   }
 
   // The state file is gone.
@@ -74,7 +74,7 @@
     // -> Give up.
     if (!ClearState()) {
       PERFETTO_ELOG("Guardrail: failed to initialize guardrail state.");
-      return false;
+      return ShouldTraceResponse::kFailedToInitState;
     }
   }
 
@@ -90,7 +90,7 @@
     ClearState();
     PERFETTO_ELOG("Guardrail: state invalid, clearing it.");
     if (!args.ignore_guardrails)
-      return false;
+      return ShouldTraceResponse::kInvalidState;
   }
 
   // First trace was more than 24h ago? Reset state.
@@ -99,7 +99,7 @@
     state_.set_first_trace_timestamp(0);
     state_.set_last_trace_timestamp(0);
     state_.set_total_bytes_uploaded(0);
-    return true;
+    return ShouldTraceResponse::kOkToTrace;
   }
 
   uint64_t max_upload_guardrail = kMaxUploadInBytes;
@@ -129,10 +129,10 @@
                   " in the last 24h. Limit is %" PRIu64 ".",
                   uploaded_so_far, max_upload_guardrail);
     if (!args.ignore_guardrails)
-      return false;
+      return ShouldTraceResponse::kHitUploadLimit;
   }
 
-  return true;
+  return ShouldTraceResponse::kOkToTrace;
 }
 
 bool RateLimiter::OnTraceDone(const Args& args, bool success, uint64_t bytes) {
@@ -142,7 +142,7 @@
   if (!success)
     return false;
 
-  if (!args.is_dropbox)
+  if (!args.is_uploading)
     return true;
 
   // If the first trace timestamp is 0 (either because this is the
@@ -207,7 +207,7 @@
     return false;
   std::string s;
   base::ReadFileDescriptor(in_fd.get(), &s);
-  if (s.size() == 0)
+  if (s.empty())
     return false;
   return state->ParseFromString(s);
 }
diff --git a/src/perfetto_cmd/rate_limiter.h b/src/perfetto_cmd/rate_limiter.h
index 5866cd1..7543090 100644
--- a/src/perfetto_cmd/rate_limiter.h
+++ b/src/perfetto_cmd/rate_limiter.h
@@ -26,18 +26,25 @@
  public:
   struct Args {
     bool is_user_build = false;
-    bool is_dropbox = false;
+    bool is_uploading = false;
     bool ignore_guardrails = false;
     bool allow_user_build_tracing = false;
     base::TimeSeconds current_time = base::TimeSeconds(0);
     uint64_t max_upload_bytes_override = 0;
     std::string unique_session_name = "";
   };
+  enum ShouldTraceResponse {
+    kOkToTrace,
+    kNotAllowedOnUserBuild,
+    kFailedToInitState,
+    kInvalidState,
+    kHitUploadLimit,
+  };
 
   RateLimiter();
   virtual ~RateLimiter();
 
-  bool ShouldTrace(const Args& args);
+  ShouldTraceResponse ShouldTrace(const Args& args);
   bool OnTraceDone(const Args& args, bool success, uint64_t bytes);
 
   bool ClearState();
diff --git a/src/perfetto_cmd/rate_limiter_unittest.cc b/src/perfetto_cmd/rate_limiter_unittest.cc
index 1f6612d..7ba499b 100644
--- a/src/perfetto_cmd/rate_limiter_unittest.cc
+++ b/src/perfetto_cmd/rate_limiter_unittest.cc
@@ -158,7 +158,7 @@
 TEST(RateLimiterTest, NotDropBox) {
   StrictMock<MockRateLimiter> limiter;
 
-  ASSERT_TRUE(limiter.ShouldTrace({}));
+  ASSERT_EQ(limiter.ShouldTrace({}), RateLimiter::kOkToTrace);
   ASSERT_TRUE(limiter.OnTraceDone({}, true, 10000));
   ASSERT_FALSE(limiter.StateFileExists());
 }
@@ -175,13 +175,13 @@
   RateLimiter::Args args;
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.ignore_guardrails = true;
   args.current_time = base::TimeSeconds(41);
 
   EXPECT_CALL(limiter, SaveState(_));
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_));
   ASSERT_TRUE(limiter.OnTraceDone(args, true, 42u));
@@ -198,12 +198,12 @@
   RateLimiter::Args args;
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(10000);
 
   EXPECT_CALL(limiter, SaveState(_));
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_));
   ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
@@ -226,11 +226,11 @@
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_));
   ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
@@ -254,12 +254,12 @@
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.unique_session_name = "foo";
   args.current_time = base::TimeSeconds(input.last_trace_timestamp() + 60 * 10);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_));
   ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
@@ -287,13 +287,13 @@
   RateLimiter::Args args;
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
 
   WriteGarbageToFile(limiter.GetStateFilePath().c_str());
 
   EXPECT_CALL(limiter, LoadState(_));
   EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState);
 
   gen::PerfettoCmdState output{};
   ASSERT_TRUE(limiter.LoadStateConcrete(&output));
@@ -312,12 +312,12 @@
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(99);
 
   EXPECT_CALL(limiter, LoadState(_));
   EXPECT_CALL(limiter, SaveState(_));
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kInvalidState);
 
   gen::PerfettoCmdState output{};
   ASSERT_TRUE(limiter.LoadStateConcrete(&output));
@@ -339,12 +339,12 @@
 
   args.is_user_build = true;
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.unique_session_name = "bar";
   args.current_time = base::TimeSeconds(60 * 60);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 }
 
 TEST(RateLimiterTest, DropBox_TooMuch_Session) {
@@ -360,12 +360,12 @@
 
   args.is_user_build = true;
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.unique_session_name = "foo";
   args.current_time = base::TimeSeconds(60 * 60);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
 }
 
 TEST(RateLimiterTest, DropBox_TooMuch_User) {
@@ -378,11 +378,11 @@
 
   args.is_user_build = true;
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(60 * 60);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
 }
 
 TEST(RateLimiterTest, DropBox_TooMuch_Override) {
@@ -396,13 +396,13 @@
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(60 * 60);
   args.max_upload_bytes_override = 10 * 1024 * 1024 + 2;
   args.unique_session_name = "foo";
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 }
 
 // Override doesn't apply to traces without session name.
@@ -415,12 +415,12 @@
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
   args.allow_user_build_tracing = true;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(60 * 60);
   args.max_upload_bytes_override = 10 * 1024 * 1024 + 2;
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kHitUploadLimit);
 }
 
 TEST(RateLimiterTest, DropBox_TooMuchWasUploaded) {
@@ -433,11 +433,11 @@
   input.set_total_bytes_uploaded(10 * 1024 * 1024 + 1);
   ASSERT_TRUE(limiter.SaveStateConcrete(input));
 
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(60 * 60 * 24 + 2);
 
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_));
   ASSERT_TRUE(limiter.OnTraceDone(args, true, 1024 * 1024));
@@ -455,12 +455,12 @@
   StrictMock<MockRateLimiter> limiter;
   RateLimiter::Args args;
 
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(10000);
 
   EXPECT_CALL(limiter, SaveState(_));
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
   ASSERT_FALSE(limiter.OnTraceDone(args, false, 1024 * 1024));
 }
 
@@ -468,12 +468,12 @@
   StrictMock<MockRateLimiter> limiter;
   RateLimiter::Args args;
 
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(10000);
 
   EXPECT_CALL(limiter, SaveState(_));
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 
   EXPECT_CALL(limiter, SaveState(_)).WillOnce(Return(false));
   ASSERT_FALSE(limiter.OnTraceDone(args, true, 1024 * 1024));
@@ -485,10 +485,10 @@
 
   args.is_user_build = true;
   args.allow_user_build_tracing = false;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(10000);
 
-  ASSERT_FALSE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kNotAllowedOnUserBuild);
 }
 
 TEST(RateLimiterTest, DropBox_CanTraceOnUser) {
@@ -497,12 +497,12 @@
 
   args.is_user_build = false;
   args.allow_user_build_tracing = false;
-  args.is_dropbox = true;
+  args.is_uploading = true;
   args.current_time = base::TimeSeconds(10000);
 
   EXPECT_CALL(limiter, SaveState(_));
   EXPECT_CALL(limiter, LoadState(_));
-  ASSERT_TRUE(limiter.ShouldTrace(args));
+  ASSERT_EQ(limiter.ShouldTrace(args), RateLimiter::kOkToTrace);
 }
 
 }  // namespace
diff --git a/src/perfetto_cmd/trigger_perfetto.cc b/src/perfetto_cmd/trigger_perfetto.cc
index cd22ea2..cbe4032 100644
--- a/src/perfetto_cmd/trigger_perfetto.cc
+++ b/src/perfetto_cmd/trigger_perfetto.cc
@@ -22,6 +22,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/traced/traced.h"
+#include "src/android_stats/statsd_logging_helper.h"
 #include "src/perfetto_cmd/trigger_producer.h"
 
 namespace perfetto {
@@ -40,8 +41,8 @@
 
 int __attribute__((visibility("default")))
 TriggerPerfettoMain(int argc, char** argv) {
-  static const struct option long_options[] = {
-      {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}};
+  static const option long_options[] = {{"help", no_argument, nullptr, 'h'},
+                                        {nullptr, 0, nullptr, 0}};
 
   int option_index = 0;
 
@@ -60,11 +61,14 @@
   for (int i = optind; i < argc; i++)
     triggers_to_activate.push_back(std::string(argv[i]));
 
-  if (triggers_to_activate.size() == 0) {
+  if (triggers_to_activate.empty()) {
     PERFETTO_ELOG("At least one trigger must the specified.");
     return PrintUsage(argv[0]);
   }
 
+  android_stats::MaybeLogTriggerEvents(
+      PerfettoTriggerAtom::kTriggerPerfettoTrigger, triggers_to_activate);
+
   bool finished_with_success = false;
   base::UnixTaskRunner task_runner;
   TriggerProducer producer(
@@ -76,7 +80,12 @@
       &triggers_to_activate);
   task_runner.Run();
 
-  return finished_with_success ? 0 : 1;
+  if (!finished_with_success) {
+    android_stats::MaybeLogTriggerEvents(
+        PerfettoTriggerAtom::kTriggerPerfettoTriggerFail, triggers_to_activate);
+    return 1;
+  }
+  return 0;
 }
 
 }  // namespace perfetto
diff --git a/src/profiling/BUILD.gn b/src/profiling/BUILD.gn
index 5a929b6..64cbbd2 100644
--- a/src/profiling/BUILD.gn
+++ b/src/profiling/BUILD.gn
@@ -20,6 +20,9 @@
   deps = [
     "../../gn:default_deps",
     "../../include/perfetto/ext/base:base",
+    "../../protos/perfetto/trace:zero",
+    "../../protos/perfetto/trace/profiling:zero",
+    "../../src/protozero:protozero",
   ]
   public_deps = [ "../../include/perfetto/profiling:deobfuscator" ]
 }
diff --git a/src/profiling/common/BUILD.gn b/src/profiling/common/BUILD.gn
index e999f29..a8b948f 100644
--- a/src/profiling/common/BUILD.gn
+++ b/src/profiling/common/BUILD.gn
@@ -76,11 +76,25 @@
   ]
 }
 
+source_set("profiler_guardrails") {
+  deps = [
+    ":proc_utils",
+    "../../../gn:default_deps",
+    "../../../include/perfetto/ext/tracing/core",
+    "../../base",
+  ]
+  sources = [
+    "profiler_guardrails.cc",
+    "profiler_guardrails.h",
+  ]
+}
+
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
     ":interner",
     ":proc_utils",
+    ":profiler_guardrails",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
     "../../../include/perfetto/profiling:normalize",
@@ -90,5 +104,6 @@
   sources = [
     "interner_unittest.cc",
     "proc_utils_unittest.cc",
+    "profiler_guardrails_unittest.cc",
   ]
 }
diff --git a/src/profiling/common/callstack_trie.cc b/src/profiling/common/callstack_trie.cc
index 01541e9..81d0063 100644
--- a/src/profiling/common/callstack_trie.cc
+++ b/src/profiling/common/callstack_trie.cc
@@ -28,9 +28,9 @@
 GlobalCallstackTrie::Node* GlobalCallstackTrie::GetOrCreateChild(
     Node* self,
     const Interned<Frame>& loc) {
-  Node* child = self->children_.Get(loc);
+  Node* child = self->GetChild(loc);
   if (!child)
-    child = self->children_.Emplace(loc, ++next_callstack_id_, self);
+    child = self->AddChild(loc, ++next_callstack_id_, self);
   return child;
 }
 
@@ -45,13 +45,19 @@
 }
 
 GlobalCallstackTrie::Node* GlobalCallstackTrie::CreateCallsite(
-    const std::vector<FrameData>& callstack) {
+    const std::vector<unwindstack::FrameData>& callstack,
+    const std::vector<std::string>& build_ids) {
+  PERFETTO_CHECK(callstack.size() == build_ids.size());
   Node* node = &root_;
   // libunwindstack gives the frames top-first, but we want to bookkeep and
   // emit as bottom first.
-  for (auto it = callstack.crbegin(); it != callstack.crend(); ++it) {
-    const FrameData& loc = *it;
-    node = GetOrCreateChild(node, InternCodeLocation(loc));
+  auto callstack_it = callstack.crbegin();
+  auto build_id_it = build_ids.crbegin();
+  for (; callstack_it != callstack.crend() && build_id_it != build_ids.crend();
+       ++callstack_it, ++build_id_it) {
+    const unwindstack::FrameData& loc = *callstack_it;
+    const std::string& build_id = *build_id_it;
+    node = GetOrCreateChild(node, InternCodeLocation(loc, build_id));
   }
   return node;
 }
@@ -82,7 +88,7 @@
   Node* prev = nullptr;
   while (node != nullptr) {
     if (delete_prev)
-      node->children_.Remove(*prev);
+      node->RemoveChild(prev);
     node->ref_count_ -= 1;
     delete_prev = node->ref_count_ == 0;
     prev = node;
@@ -90,20 +96,21 @@
   }
 }
 
-Interned<Frame> GlobalCallstackTrie::InternCodeLocation(const FrameData& loc) {
-  Mapping map(string_interner_.Intern(loc.build_id));
-  map.exact_offset = loc.frame.map_exact_offset;
-  map.start_offset = loc.frame.map_elf_start_offset;
-  map.start = loc.frame.map_start;
-  map.end = loc.frame.map_end;
-  map.load_bias = loc.frame.map_load_bias;
-  base::StringSplitter sp(loc.frame.map_name, '/');
+Interned<Frame> GlobalCallstackTrie::InternCodeLocation(
+    const unwindstack::FrameData& loc,
+    const std::string& build_id) {
+  Mapping map(string_interner_.Intern(build_id));
+  map.exact_offset = loc.map_exact_offset;
+  map.start_offset = loc.map_elf_start_offset;
+  map.start = loc.map_start;
+  map.end = loc.map_end;
+  map.load_bias = loc.map_load_bias;
+  base::StringSplitter sp(loc.map_name, '/');
   while (sp.Next())
     map.path_components.emplace_back(string_interner_.Intern(sp.cur_token()));
 
   Frame frame(mapping_interner_.Intern(std::move(map)),
-              string_interner_.Intern(loc.frame.function_name),
-              loc.frame.rel_pc);
+              string_interner_.Intern(loc.function_name), loc.rel_pc);
 
   return frame_interner_.Intern(frame);
 }
@@ -117,5 +124,30 @@
   return frame_interner_.Intern(frame);
 }
 
+GlobalCallstackTrie::Node* GlobalCallstackTrie::Node::AddChild(
+    const Interned<Frame>& loc,
+    uint64_t callstack_id,
+    Node* parent) {
+  auto it = children_.emplace(loc, callstack_id, parent);
+  return const_cast<Node*>(&(*it.first));
+}
+void GlobalCallstackTrie::Node::RemoveChild(Node* node) {
+  children_.erase(*node);
+}
+
+GlobalCallstackTrie::Node* GlobalCallstackTrie::Node::GetChild(
+    const Interned<Frame>& loc) {
+  // This will be nicer with C++14 transparent comparators.
+  // Then we will be able to look up by just the key using a sutiable
+  // comparator.
+  //
+  // For now we need to allow to construct Node from the key.
+  Node node(loc);
+  auto it = children_.find(node);
+  if (it == children_.end())
+    return nullptr;
+  return const_cast<Node*>(&(*it));
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/common/callstack_trie.h b/src/profiling/common/callstack_trie.h
index d711cf5..f8ca304 100644
--- a/src/profiling/common/callstack_trie.h
+++ b/src/profiling/common/callstack_trie.h
@@ -17,11 +17,13 @@
 #ifndef SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_
 #define SRC_PROFILING_COMMON_CALLSTACK_TRIE_H_
 
+#include <set>
 #include <string>
 #include <typeindex>
 #include <vector>
 
-#include "perfetto/ext/base/lookup_set.h"
+#include <unwindstack/Unwinder.h>
+
 #include "src/profiling/common/interner.h"
 #include "src/profiling/common/unwind_support.h"
 
@@ -29,7 +31,7 @@
 namespace profiling {
 
 struct Mapping {
-  Mapping(Interned<std::string> b) : build_id(std::move(b)) {}
+  explicit Mapping(Interned<std::string> b) : build_id(std::move(b)) {}
 
   Interned<std::string> build_id;
   uint64_t exact_offset = 0;
@@ -103,8 +105,11 @@
     // This is opaque except to GlobalCallstackTrie.
     friend class GlobalCallstackTrie;
 
-    // Allow building a node out of a frame for base::LookupSet.
-    Node(Interned<Frame> frame) : Node(frame, 0, nullptr) {}
+    // Allow building a node out of a frame for GetChild.
+    explicit Node(Interned<Frame> frame) : Node(frame, 0, nullptr) {}
+    Node(const Node&) = default;
+    Node(Node&&) = default;
+
     Node(Interned<Frame> frame, uint64_t id)
         : Node(std::move(frame), id, nullptr) {}
     Node(Interned<Frame> frame, uint64_t id, Node* parent)
@@ -117,13 +122,26 @@
    private:
     Node* GetOrCreateChild(const Interned<Frame>& loc);
     // Deletes all descendant nodes, regardless of |ref_count_|.
-    void DeleteChildren() { children_.Clear(); }
+    void DeleteChildren() { children_.clear(); }
 
     uint64_t ref_count_ = 0;
     uint64_t id_;
     Node* const parent_;
     const Interned<Frame> location_;
-    base::LookupSet<Node, const Interned<Frame>, &Node::location_> children_;
+
+    class NodeComparator {
+     public:
+      bool operator()(const Node& one, const Node& other) const {
+        return one.location_ < other.location_;
+      }
+    };
+    Node* AddChild(const Interned<Frame>& loc,
+                   uint64_t next_callstack_id_,
+                   Node* parent);
+    void RemoveChild(Node* node);
+    Node* GetChild(const Interned<Frame>& loc);
+
+    std::set<Node, NodeComparator> children_;
   };
 
   GlobalCallstackTrie() = default;
@@ -135,9 +153,11 @@
   GlobalCallstackTrie(GlobalCallstackTrie&&) = delete;
   GlobalCallstackTrie& operator=(GlobalCallstackTrie&&) = delete;
 
-  Interned<Frame> InternCodeLocation(const FrameData& loc);
+  Interned<Frame> InternCodeLocation(const unwindstack::FrameData& loc,
+                                     const std::string& build_id);
 
-  Node* CreateCallsite(const std::vector<FrameData>& callstack);
+  Node* CreateCallsite(const std::vector<unwindstack::FrameData>& callstack,
+                       const std::vector<std::string>& build_ids);
   Node* CreateCallsite(const std::vector<Interned<Frame>>& callstack);
 
   static void IncrementNode(Node* node);
diff --git a/src/profiling/common/interner.h b/src/profiling/common/interner.h
index 7490ef2..636b103 100644
--- a/src/profiling/common/interner.h
+++ b/src/profiling/common/interner.h
@@ -57,7 +57,7 @@
   class Interned {
    public:
     friend class Interner<T>;
-    Interned(Entry* entry) : entry_(entry) {}
+    explicit Interned(Entry* entry) : entry_(entry) {}
     Interned(const Interned& other) : entry_(other.entry_) {
       if (entry_ != nullptr)
         entry_->ref_count++;
diff --git a/src/profiling/common/interning_output.cc b/src/profiling/common/interning_output.cc
index 778f61e..a4d1850 100644
--- a/src/profiling/common/interning_output.cc
+++ b/src/profiling/common/interning_output.cc
@@ -78,6 +78,8 @@
 
 void InterningOutputTracker::WriteFrame(Interned<Frame> frame,
                                         protos::pbzero::InternedData* out) {
+  // Trace processor depends on the map being written before the
+  // frame. See StackProfileTracker::AddFrame.
   WriteMap(frame->mapping, out);
   WriteFunctionNameString(frame->function_name, out);
   bool inserted;
diff --git a/src/profiling/common/profiler_guardrails.cc b/src/profiling/common/profiler_guardrails.cc
new file mode 100644
index 0000000..a7f5dad
--- /dev/null
+++ b/src/profiling/common/profiler_guardrails.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/common/profiler_guardrails.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/watchdog_posix.h"
+
+namespace perfetto {
+namespace profiling {
+
+base::Optional<uint64_t> GetCputimeSecForCurrentProcess() {
+  return GetCputimeSecForCurrentProcess(
+      base::OpenFile("/proc/self/stat", O_RDONLY));
+}
+
+base::Optional<uint64_t> GetCputimeSecForCurrentProcess(
+    base::ScopedFile stat_fd) {
+  if (!stat_fd)
+    return base::nullopt;
+  base::ProcStat stat;
+  if (!ReadProcStat(stat_fd.get(), &stat)) {
+    PERFETTO_ELOG("Failed to read stat file to enforce guardrails.");
+    return base::nullopt;
+  }
+  return (stat.utime + stat.stime) /
+         static_cast<unsigned long>(sysconf(_SC_CLK_TCK));
+}
+
+ProfilerMemoryGuardrails::ProfilerMemoryGuardrails()
+    : ProfilerMemoryGuardrails(base::OpenFile("/proc/self/status", O_RDONLY)) {}
+
+ProfilerMemoryGuardrails::ProfilerMemoryGuardrails(base::ScopedFile status_fd) {
+  std::string status;
+  if (base::ReadFileDescriptor(*status_fd, &status))
+    anon_and_swap_ = GetRssAnonAndSwap(status);
+
+  if (!anon_and_swap_) {
+    PERFETTO_ELOG("Failed to read memory usage.");
+    return;
+  }
+}
+
+bool ProfilerMemoryGuardrails::IsOverMemoryThreshold(
+    const GuardrailConfig& ds) {
+  uint32_t ds_max_mem = ds.memory_guardrail_kb;
+  if (!ds_max_mem || !anon_and_swap_)
+    return false;
+
+  if (ds_max_mem > 0 && *anon_and_swap_ > ds_max_mem) {
+    PERFETTO_ELOG("Exceeded data-source memory guardrail (%" PRIu32
+                  " > %" PRIu32 "). Shutting down.",
+                  *anon_and_swap_, ds_max_mem);
+    return true;
+  }
+  return false;
+}
+
+ProfilerCpuGuardrails::ProfilerCpuGuardrails() {
+  opt_cputime_sec_ = GetCputimeSecForCurrentProcess();
+  if (!opt_cputime_sec_) {
+    PERFETTO_ELOG("Failed to get CPU time.");
+  }
+}
+
+// For testing.
+ProfilerCpuGuardrails::ProfilerCpuGuardrails(base::ScopedFile stat_fd) {
+  opt_cputime_sec_ = GetCputimeSecForCurrentProcess(std::move(stat_fd));
+  if (!opt_cputime_sec_) {
+    PERFETTO_ELOG("Failed to get CPU time.");
+  }
+}
+
+bool ProfilerCpuGuardrails::IsOverCpuThreshold(const GuardrailConfig& ds) {
+  uint64_t ds_max_cpu = ds.cpu_guardrail_sec;
+  if (!ds_max_cpu || !opt_cputime_sec_)
+    return false;
+  uint64_t cputime_sec = *opt_cputime_sec_;
+
+  auto start_cputime_sec = ds.cpu_start_secs;
+  // We reject data-sources with CPU guardrails if we cannot read the
+  // initial value, which means we get a non-nullopt value here.
+  PERFETTO_CHECK(start_cputime_sec);
+  uint64_t cpu_diff = cputime_sec - *start_cputime_sec;
+  if (cputime_sec > *start_cputime_sec && cpu_diff > ds_max_cpu) {
+    PERFETTO_ELOG("Exceeded data-source CPU guardrail (%" PRIu64 " > %" PRIu64
+                  "). Shutting down.",
+                  cpu_diff, ds_max_cpu);
+    return true;
+  }
+  return false;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/common/profiler_guardrails.h b/src/profiling/common/profiler_guardrails.h
new file mode 100644
index 0000000..f89c0c2
--- /dev/null
+++ b/src/profiling/common/profiler_guardrails.h
@@ -0,0 +1,71 @@
+/*
+ * 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_PROFILING_COMMON_PROFILER_GUARDRAILS_H_
+#define SRC_PROFILING_COMMON_PROFILER_GUARDRAILS_H_
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
+#include "src/profiling/common/proc_utils.h"
+
+namespace perfetto {
+namespace profiling {
+
+base::Optional<uint64_t> GetCputimeSecForCurrentProcess();
+// For testing.
+base::Optional<uint64_t> GetCputimeSecForCurrentProcess(
+    base::ScopedFile stat_fd);
+
+struct GuardrailConfig {
+  uint64_t cpu_guardrail_sec = 0;
+  base::Optional<uint64_t> cpu_start_secs;
+  uint32_t memory_guardrail_kb = 0;
+};
+
+class ProfilerCpuGuardrails {
+ public:
+  ProfilerCpuGuardrails();
+  // Allows to supply custom stat fd for testing.
+  explicit ProfilerCpuGuardrails(base::ScopedFile stat_fd);
+
+  bool IsOverCpuThreshold(const GuardrailConfig& ds);
+
+ private:
+  base::Optional<uint64_t> opt_cputime_sec_;
+};
+
+class ProfilerMemoryGuardrails {
+ public:
+  ProfilerMemoryGuardrails();
+  // Allows to supply custom status fd for testing.
+  explicit ProfilerMemoryGuardrails(base::ScopedFile status_fd);
+
+  bool IsOverMemoryThreshold(const GuardrailConfig& ds);
+
+ private:
+  base::Optional<uint32_t> anon_and_swap_;
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_COMMON_PROFILER_GUARDRAILS_H_
diff --git a/src/profiling/common/profiler_guardrails_unittest.cc b/src/profiling/common/profiler_guardrails_unittest.cc
new file mode 100644
index 0000000..ef8a450
--- /dev/null
+++ b/src/profiling/common/profiler_guardrails_unittest.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/common/profiler_guardrails.h"
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <map>
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/temp_file.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+TEST(ProfilerCpuGuardrailsTest, Exceeded) {
+  const auto clk = static_cast<unsigned long>(sysconf(_SC_CLK_TCK));
+  base::TempFile f = base::TempFile::CreateUnlinked();
+  constexpr const char stat[] =
+      "2965981 (zsh) S 2965977 2965981 2965981 34822 2966607 4194304 6632 6697 "
+      "0 0 1000000 6000000 4 1 20 0 1 0 227163466 15839232 2311 "
+      "18446744073709551615 "
+      "94823961161728 94823961762781 140722993535472 0 0 0 2 3686400 134295555 "
+      "0 0 0 17 2 0 0 0 0 0 94823961905904 94823961935208 94823993954304 "
+      "140722993543678 140722993543691 140722993543691 140722993545195 0";
+  base::WriteAll(f.fd(), stat, sizeof(stat));
+  ASSERT_NE(lseek(f.fd(), 0, SEEK_SET), -1);
+  ProfilerCpuGuardrails gr(f.ReleaseFD());
+
+  GuardrailConfig gc;
+  gc.cpu_guardrail_sec = 5000000 / clk;
+  gc.cpu_start_secs = 1000000 / clk;
+  EXPECT_TRUE(gr.IsOverCpuThreshold(gc));
+}
+
+TEST(ProfilerCpuGuardrailsTest, NotExceeded) {
+  const auto clk = static_cast<unsigned long>(sysconf(_SC_CLK_TCK));
+  base::TempFile f = base::TempFile::CreateUnlinked();
+  constexpr const char stat[] =
+      "2965981 (zsh) S 2965977 2965981 2965981 34822 2966607 4194304 6632 6697 "
+      "0 0 1000000 6000000 4 1 20 0 1 0 227163466 15839232 2311 "
+      "18446744073709551615 "
+      "94823961161728 94823961762781 140722993535472 0 0 0 2 3686400 134295555 "
+      "0 0 0 17 2 0 0 0 0 0 94823961905904 94823961935208 94823993954304 "
+      "140722993543678 140722993543691 140722993543691 140722993545195 0";
+  base::WriteAll(f.fd(), stat, sizeof(stat));
+  ASSERT_NE(lseek(f.fd(), 0, SEEK_SET), -1);
+  ProfilerCpuGuardrails gr(f.ReleaseFD());
+
+  GuardrailConfig gc;
+  gc.cpu_guardrail_sec = 7000000 / clk;
+  gc.cpu_start_secs = 1000000 / clk;
+  EXPECT_FALSE(gr.IsOverCpuThreshold(gc));
+}
+
+TEST(ProfilerMemoryGuardrailsTest, Exceeded) {
+  base::TempFile f = base::TempFile::CreateUnlinked();
+  constexpr const char status[] =
+      "VmPeak:\t    5432 kB\n"
+      "VmSize:\t    5432 kB\n"
+      "VmLck:\t       0 kB\n"
+      "VmPin:\t       0 kB\n"
+      "VmHWM:\t     584 kB\n"
+      "VmRSS:\t     80 kB\n"
+      "RssAnon:\t      68 kB\n"
+      "RssFile:\t     516 kB\n"
+      "RssShmem:\t       0 kB\n"
+      "VmData:\t     316 kB\n"
+      "VmStk:\t     132 kB\n"
+      "VmExe:\t      20 kB\n"
+      "VmLib:\t    1460 kB\n"
+      "VmPTE:\t      44 kB\n"
+      "VmSwap:\t       10 kB\n";
+
+  base::WriteAll(f.fd(), status, sizeof(status));
+  ASSERT_NE(lseek(f.fd(), 0, SEEK_SET), -1);
+  ProfilerMemoryGuardrails gr(f.ReleaseFD());
+
+  GuardrailConfig gc;
+  gc.memory_guardrail_kb = 77;
+  EXPECT_TRUE(gr.IsOverMemoryThreshold(gc));
+}
+
+TEST(ProfilerMemoryGuardrailsTest, NotExceeded) {
+  base::TempFile f = base::TempFile::CreateUnlinked();
+  constexpr const char status[] =
+      "VmPeak:\t    5432 kB\n"
+      "VmSize:\t    5432 kB\n"
+      "VmLck:\t       0 kB\n"
+      "VmPin:\t       0 kB\n"
+      "VmHWM:\t     584 kB\n"
+      "VmRSS:\t     80 kB\n"
+      "RssAnon:\t      68 kB\n"
+      "RssFile:\t     516 kB\n"
+      "RssShmem:\t       0 kB\n"
+      "VmData:\t     316 kB\n"
+      "VmStk:\t     132 kB\n"
+      "VmExe:\t      20 kB\n"
+      "VmLib:\t    1460 kB\n"
+      "VmPTE:\t      44 kB\n"
+      "VmSwap:\t       10 kB\n";
+
+  base::WriteAll(f.fd(), status, sizeof(status));
+  ASSERT_NE(lseek(f.fd(), 0, SEEK_SET), -1);
+  ProfilerMemoryGuardrails gr(f.ReleaseFD());
+  GuardrailConfig gc;
+  gc.memory_guardrail_kb = 100;
+  EXPECT_FALSE(gr.IsOverMemoryThreshold(gc));
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/common/unwind_support.cc b/src/profiling/common/unwind_support.cc
index c75fbd0..dee50de 100644
--- a/src/profiling/common/unwind_support.cc
+++ b/src/profiling/common/unwind_support.cc
@@ -122,15 +122,15 @@
 #endif
 }
 
-FrameData UnwindingMetadata::AnnotateFrame(unwindstack::FrameData frame) {
-  std::string build_id;
+const std::string& UnwindingMetadata::GetBuildId(
+    const unwindstack::FrameData& frame) {
   if (!frame.map_name.empty()) {
     unwindstack::MapInfo* map_info = fd_maps.Find(frame.pc);
     if (map_info)
-      build_id = map_info->GetBuildID();
+      return map_info->GetBuildID();
   }
 
-  return FrameData{std::move(frame), std::move(build_id)};
+  return empty_string_;
 }
 
 std::string StringifyLibUnwindstackError(unwindstack::ErrorCode e) {
@@ -151,6 +151,12 @@
       return "REPEATED_FRAME";
     case unwindstack::ERROR_INVALID_ELF:
       return "INVALID_ELF";
+    case unwindstack::ERROR_SYSTEM_CALL:
+      return "SYSTEM_CALL";
+    case unwindstack::ERROR_THREAD_DOES_NOT_EXIST:
+      return "THREAD_DOES_NOT_EXIST";
+    case unwindstack::ERROR_THREAD_TIMEOUT:
+      return "THREAD_TIMEOUT";
   }
 }
 
diff --git a/src/profiling/common/unwind_support.h b/src/profiling/common/unwind_support.h
index 76a2652..bd8064d 100644
--- a/src/profiling/common/unwind_support.h
+++ b/src/profiling/common/unwind_support.h
@@ -37,20 +37,11 @@
 namespace perfetto {
 namespace profiling {
 
-// libunwindstack's FrameData annotated with the build_id.
-struct FrameData {
-  FrameData(unwindstack::FrameData f, std::string id)
-      : frame(std::move(f)), build_id(std::move(id)) {}
-
-  unwindstack::FrameData frame;
-  std::string build_id;
-};
-
 // Read /proc/[pid]/maps from an open file descriptor.
 // TODO(fmayer): Figure out deduplication to other maps.
 class FDMaps : public unwindstack::Maps {
  public:
-  FDMaps(base::ScopedFile fd);
+  explicit FDMaps(base::ScopedFile fd);
 
   FDMaps(const FDMaps&) = delete;
   FDMaps& operator=(const FDMaps&) = delete;
@@ -75,7 +66,7 @@
 
 class FDMemory : public unwindstack::Memory {
  public:
-  FDMemory(base::ScopedFile mem_fd);
+  explicit FDMemory(base::ScopedFile mem_fd);
   size_t Read(uint64_t addr, void* dst, size_t size) override;
 
  private:
@@ -112,8 +103,9 @@
 
   void ReparseMaps();
 
-  FrameData AnnotateFrame(unwindstack::FrameData frame);
+  const std::string& GetBuildId(const unwindstack::FrameData& frame);
 
+  std::string empty_string_;
   FDMaps fd_maps;
   // The API of libunwindstack expects shared_ptr for Memory.
   std::shared_ptr<unwindstack::Memory> fd_mem;
diff --git a/src/profiling/deobfuscator.cc b/src/profiling/deobfuscator.cc
index a8b7652..862e4a2 100644
--- a/src/profiling/deobfuscator.cc
+++ b/src/profiling/deobfuscator.cc
@@ -15,16 +15,26 @@
  */
 
 #include "perfetto/profiling/deobfuscator.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_splitter.h"
 
 #include "perfetto/ext/base/optional.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
 namespace profiling {
 namespace {
 
-base::Optional<std::pair<std::string, std::string>> ParseClass(
-    std::string line) {
+struct ProguardClass {
+  std::string obfuscated_name;
+  std::string deobfuscated_name;
+};
+
+base::Optional<ProguardClass> ParseClass(std::string line) {
   base::StringSplitter ss(std::move(line), ' ');
 
   if (!ss.Next()) {
@@ -44,7 +54,7 @@
     return base::nullopt;
   }
   std::string obfuscated_name(ss.cur_token(), ss.cur_token_size());
-  if (obfuscated_name.size() == 0) {
+  if (obfuscated_name.empty()) {
     PERFETTO_ELOG("Empty obfuscated name.");
     return base::nullopt;
   }
@@ -58,12 +68,22 @@
     PERFETTO_ELOG("Unexpected data.");
     return base::nullopt;
   }
-  return std::make_pair(std::move(obfuscated_name),
-                        std::move(deobfuscated_name));
+  return ProguardClass{std::move(obfuscated_name),
+                       std::move(deobfuscated_name)};
 }
 
-base::Optional<std::pair<std::string, std::string>> ParseMember(
-    std::string line) {
+enum class ProguardMemberType {
+  kField,
+  kMethod,
+};
+
+struct ProguardMember {
+  ProguardMemberType type;
+  std::string obfuscated_name;
+  std::string deobfuscated_name;
+};
+
+base::Optional<ProguardMember> ParseMember(std::string line) {
   base::StringSplitter ss(std::move(line), ' ');
 
   if (!ss.Next()) {
@@ -94,12 +114,48 @@
     PERFETTO_ELOG("Unexpected data.");
     return base::nullopt;
   }
-  return std::make_pair(std::move(obfuscated_name),
-                        std::move(deobfuscated_name));
+
+  ProguardMemberType member_type;
+  auto paren_idx = deobfuscated_name.find("(");
+  if (paren_idx != std::string::npos) {
+    member_type = ProguardMemberType::kMethod;
+    deobfuscated_name = deobfuscated_name.substr(0, paren_idx);
+    auto colon_idx = type_name.find(":");
+    if (colon_idx != std::string::npos) {
+      type_name = type_name.substr(colon_idx + 1);
+    }
+  } else {
+    member_type = ProguardMemberType::kField;
+  }
+  return ProguardMember{member_type, std::move(obfuscated_name),
+                        std::move(deobfuscated_name)};
+}
+
+std::string FlattenMethods(const std::vector<std::string>& v) {
+  if (v.size() == 1) {
+    return v[0];
+  }
+  return "[ambiguous]";
 }
 
 }  // namespace
 
+std::string FlattenClasses(
+    const std::map<std::string, std::vector<std::string>>& m) {
+  std::string result;
+  bool first = true;
+  for (const auto& p : m) {
+    if (!first) {
+      result += " | ";
+    }
+    result += p.first + "." + FlattenMethods(p.second);
+    first = false;
+  }
+  return result;
+}
+
+// See https://www.guardsquare.com/en/products/proguard/manual/retrace for the
+// file format we are parsing.
 bool ProguardParser::AddLine(std::string line) {
   if (line.length() == 0)
     return true;
@@ -109,42 +165,131 @@
     return false;
   }
   if (!is_member) {
-    std::string obfuscated_name;
-    std::string deobfuscated_name;
-    auto opt_pair = ParseClass(std::move(line));
-    if (!opt_pair)
+    auto opt_cls = ParseClass(std::move(line));
+    if (!opt_cls)
       return false;
-    std::tie(obfuscated_name, deobfuscated_name) = *opt_pair;
-    auto p = mapping_.emplace(std::move(obfuscated_name),
-                              std::move(deobfuscated_name));
+    auto p = mapping_.emplace(std::move(opt_cls->obfuscated_name),
+                              std::move(opt_cls->deobfuscated_name));
     if (!p.second) {
       PERFETTO_ELOG("Duplicate class.");
       return false;
     }
     current_class_ = &p.first->second;
   } else {
-    std::string obfuscated_name;
-    std::string deobfuscated_name;
-    auto opt_pair = ParseMember(std::move(line));
-    if (!opt_pair)
+    auto opt_member = ParseMember(std::move(line));
+    if (!opt_member)
       return false;
-    std::tie(obfuscated_name, deobfuscated_name) = *opt_pair;
-    // TODO(fmayer): Teach this to properly parse methods.
-    if (deobfuscated_name.find("(") != std::string::npos) {
-      // Skip functions, as they will trigger the "Duplicate member" below.
-      return true;
-    }
-    auto p = current_class_->deobfuscated_fields.emplace(obfuscated_name,
-                                                         deobfuscated_name);
-    if (!p.second && p.first->second != deobfuscated_name) {
-      PERFETTO_ELOG("Member redefinition: %s.%s",
-                    current_class_->deobfuscated_name.c_str(),
-                    deobfuscated_name.c_str());
-      return false;
+    switch (opt_member->type) {
+      case (ProguardMemberType::kField): {
+        if (!current_class_->AddField(opt_member->obfuscated_name,
+                                      opt_member->deobfuscated_name)) {
+          PERFETTO_ELOG("Member redefinition: %s.%s. Proguard map invalid",
+                        current_class_->deobfuscated_name().c_str(),
+                        opt_member->deobfuscated_name.c_str());
+          return false;
+        }
+        break;
+      }
+      case (ProguardMemberType::kMethod): {
+        current_class_->AddMethod(opt_member->obfuscated_name,
+                                  opt_member->deobfuscated_name);
+        break;
+      }
     }
   }
   return true;
 }
 
+bool ProguardParser::AddLines(std::string contents) {
+  for (base::StringSplitter lines(std::move(contents), '\n'); lines.Next();) {
+    if (!AddLine(lines.cur_token()))
+      return false;
+  }
+  return true;
+}
+
+void MakeDeobfuscationPackets(
+    const std::string& package_name,
+    const std::map<std::string, profiling::ObfuscatedClass>& mapping,
+    std::function<void(const std::string&)> callback) {
+  protozero::HeapBuffered<perfetto::protos::pbzero::Trace> trace;
+  auto* packet = trace->add_packet();
+  // TODO(fmayer): Add handling for package name and version code here so we
+  // can support multiple dumps in the same trace.
+  auto* proto_mapping = packet->set_deobfuscation_mapping();
+  proto_mapping->set_package_name(package_name);
+  for (const auto& p : mapping) {
+    const std::string& obfuscated_class_name = p.first;
+    const profiling::ObfuscatedClass& cls = p.second;
+
+    auto* proto_class = proto_mapping->add_obfuscated_classes();
+    proto_class->set_obfuscated_name(obfuscated_class_name);
+    proto_class->set_deobfuscated_name(cls.deobfuscated_name());
+    for (const auto& field_p : cls.deobfuscated_fields()) {
+      const std::string& obfuscated_field_name = field_p.first;
+      const std::string& deobfuscated_field_name = field_p.second;
+      auto* proto_member = proto_class->add_obfuscated_members();
+      proto_member->set_obfuscated_name(obfuscated_field_name);
+      proto_member->set_deobfuscated_name(deobfuscated_field_name);
+    }
+    for (const auto& field_p : cls.deobfuscated_methods()) {
+      const std::string& obfuscated_method_name = field_p.first;
+      const std::string& deobfuscated_method_name = field_p.second;
+      auto* proto_member = proto_class->add_obfuscated_methods();
+      proto_member->set_obfuscated_name(obfuscated_method_name);
+      proto_member->set_deobfuscated_name(deobfuscated_method_name);
+    }
+  }
+  callback(trace.SerializeAsString());
+}
+
+bool ReadProguardMapsToDeobfuscationPackets(
+    const std::vector<ProguardMap>& maps,
+    std::function<void(std::string)> fn) {
+  for (const ProguardMap& map : maps) {
+    const char* filename = map.filename.c_str();
+    base::ScopedFstream f(fopen(filename, "re"));
+    if (!f) {
+      PERFETTO_ELOG("Failed to open %s", filename);
+      return false;
+    }
+    profiling::ProguardParser parser;
+    std::string contents;
+    PERFETTO_CHECK(base::ReadFileStream(*f, &contents));
+    if (!parser.AddLines(std::move(contents))) {
+      PERFETTO_ELOG("Failed to parse %s", filename);
+      return false;
+    }
+    std::map<std::string, profiling::ObfuscatedClass> obfuscation_map =
+        parser.ConsumeMapping();
+
+    // TODO(fmayer): right now, we don't use the profile we are given. We can
+    // filter the output to only contain the classes actually seen in the
+    // profile.
+    MakeDeobfuscationPackets(map.package, obfuscation_map, fn);
+  }
+  return true;
+}
+
+std::vector<ProguardMap> GetPerfettoProguardMapPath() {
+  const char* env = getenv("PERFETTO_PROGUARD_MAP");
+  if (env == nullptr)
+    return {};
+  std::vector<ProguardMap> res;
+  for (base::StringSplitter sp(std::string(env), ':'); sp.Next();) {
+    std::string token(sp.cur_token(), sp.cur_token_size());
+    size_t eq = token.find('=');
+    if (eq == std::string::npos) {
+      PERFETTO_ELOG(
+          "Invalid PERFETTO_PROGUARD_MAP. "
+          "Expected format packagename=filename[:packagename=filename...], "
+          "e.g. com.example.package1=foo.txt:com.example.package2=bar.txt.");
+      return {};
+    }
+    res.emplace_back(ProguardMap{token.substr(0, eq), token.substr(eq + 1)});
+  }
+  return res;  // for Wreturn-std-move-in-c++11.
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/deobfuscator_unittest.cc b/src/profiling/deobfuscator_unittest.cc
index 0d9619a..be35060 100644
--- a/src/profiling/deobfuscator_unittest.cc
+++ b/src/profiling/deobfuscator_unittest.cc
@@ -23,13 +23,17 @@
 
 bool operator==(const ObfuscatedClass& a, const ObfuscatedClass& b);
 bool operator==(const ObfuscatedClass& a, const ObfuscatedClass& b) {
-  return a.deobfuscated_name == b.deobfuscated_name &&
-         a.deobfuscated_fields == b.deobfuscated_fields;
+  return a.deobfuscated_name() == b.deobfuscated_name() &&
+         a.deobfuscated_fields() == b.deobfuscated_fields() &&
+         a.deobfuscated_methods() == b.deobfuscated_methods();
 }
 
 namespace {
 
+using ::testing::_;
 using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Pair;
 
 TEST(ProguardParserTest, ReadClass) {
   ProguardParser p;
@@ -64,7 +68,8 @@
       p.ConsumeMapping(),
       ElementsAre(std::pair<std::string, ObfuscatedClass>(
           "android.arch.a.a.a", {"android.arch.core.executor.ArchTaskExecutor",
-                                 std::move(deobfuscated_fields)})));
+                                 std::move(deobfuscated_fields),
+                                 {}})));
 }
 
 TEST(ProguardParserTest, Method) {
@@ -72,6 +77,78 @@
   ASSERT_TRUE(p.AddLine(
       "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
   ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  auto mapping = p.ConsumeMapping();
+  ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
+  EXPECT_THAT(
+      mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
+      ElementsAre(Pair(
+          "b", "android.arch.core.executor.ArchTaskExecutor.isMainThread")));
+}
+
+TEST(ProguardParserTest, AmbiguousMethodSameCls) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean somethingDifferent(int):116:116 -> b"));
+  auto mapping = p.ConsumeMapping();
+  ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
+  EXPECT_THAT(
+      mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
+      ElementsAre(Pair(
+          "b", "android.arch.core.executor.ArchTaskExecutor.[ambiguous]")));
+}
+
+TEST(ProguardParserTest, AmbiguousMethodDifferentCls) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
+  auto mapping = p.ConsumeMapping();
+  ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
+  EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
+              ElementsAre(Pair(
+                  "b",
+                  "Foo.somethingDifferent | "
+                  "android.arch.core.executor.ArchTaskExecutor.isMainThread")));
+}
+
+TEST(ProguardParserTest, AmbiguousMethodSameAndDifferentCls) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
+  auto mapping = p.ConsumeMapping();
+  ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
+  EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
+              ElementsAre(Pair(
+                  "b",
+                  "Foo.somethingDifferent | "
+                  "android.arch.core.executor.ArchTaskExecutor.[ambiguous]")));
+}
+
+TEST(ProguardParserTest, AmbiguousMethodSameAndDifferentCls2) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean what(String):116:116 -> b"));
+  ASSERT_TRUE(
+      p.AddLine("    15:15:boolean Foo.somethingDifferent(int):116:116 -> b"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean Foo.third(int,int):116:116 -> b"));
+  auto mapping = p.ConsumeMapping();
+  ASSERT_THAT(mapping, ElementsAre(Pair("android.arch.a.a.a", _)));
+  EXPECT_THAT(mapping.find("android.arch.a.a.a")->second.deobfuscated_methods(),
+              ElementsAre(Pair(
+                  "b",
+                  "Foo.[ambiguous] | "
+                  "android.arch.core.executor.ArchTaskExecutor.[ambiguous]")));
 }
 
 TEST(ProguardParserTest, DuplicateClass) {
@@ -92,6 +169,14 @@
       p.AddLine("    android.arch.core.executor.TaskExecutor mDelegate2 -> b"));
 }
 
+TEST(ProguardParserTest, DuplicateMethod) {
+  ProguardParser p;
+  ASSERT_TRUE(p.AddLine(
+      "android.arch.core.executor.ArchTaskExecutor -> android.arch.a.a.a:"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean isMainThread():116:116 -> b"));
+  ASSERT_TRUE(p.AddLine("    15:15:boolean doSomething(boolean):116:116 -> b"));
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 5dd107c..3933798 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -55,6 +55,8 @@
   # This will export publicly visible symbols for the malloc_hooks.
   source_set("malloc_hooks") {
     deps = [
+      ":bionic_libc_platform_headers_on_android",
+      ":wrap_allocators",
       "../../../gn:default_deps",
       "../../base",
     ]
@@ -74,20 +76,44 @@
   sources = [ "heapprofd_standalone_client_example.cc" ]
 }
 
-shared_library("heapprofd_standalone_client") {
+source_set("client_api_standalone") {
   deps = [
-    ":client_ext",
+    ":client_api",
     ":daemon",
     "../../../gn:default_deps",
     "../../../include/perfetto/ext/tracing/ipc",
     "../../base",
     "../common:proc_utils",
   ]
+  sources = [ "client_api_standalone.cc" ]
+}
+
+# This can be used to instrument custom allocators to report their allocations
+# to Perfetto. This bundles a copy of heapprofd in the library, in contrast to
+# heapprofd_client_api (see below), which expects one to be present in the
+# Android platform.
+shared_library("heapprofd_standalone_client") {
+  deps = [
+    ":client_api_standalone",
+    "../../../gn:default_deps",
+  ]
   ldflags = [
     "-Wl,--version-script",
     rebase_path("heapprofd_client_api.map.txt", root_build_dir),
   ]
-  sources = [ "client_ext_standalone.cc" ]
+}
+
+shared_library("heapprofd_api_noop") {
+  deps = [ "../../../gn:default_deps" ]
+  ldflags = [
+    "-Wl,--version-script",
+    rebase_path("heapprofd_client_api.map.txt", root_build_dir),
+  ]
+
+  # This target does absolutely nothing, so we do not want to depend on
+  # liblog.
+  configs -= [ "//gn/standalone:android_liblog" ]
+  sources = [ "client_api_noop.cc" ]
 }
 
 shared_library("heapprofd_client_api") {
@@ -100,13 +126,34 @@
     # TODO(fmayer): Add async-safe logging for non-Android.
     cflags = [ "-DPERFETTO_DISABLE_LOG" ]
   }
-  deps = [ ":client_ext" ]
+  deps = [ ":client_api" ]
   if (perfetto_build_with_android) {
-    sources = [ "client_ext_android.cc" ]
+    sources = [ "client_api_android.cc" ]
   }
 }
 
-source_set("client_ext") {
+# On GLibc Linux, this can be used to override the allocators using
+# LD_PRELOAD. On non-GLibc, this will probably fail to link.
+shared_library("heapprofd_preload") {
+  deps = [
+    ":client_api_standalone",
+    ":wrap_allocators",
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+  ldflags = [
+    "-Wl,--version-script",
+    rebase_path("heapprofd_preload.map.txt", root_build_dir),
+  ]
+  sources = [ "malloc_preload.cc" ]
+}
+
+# On Android builds, this is converted to
+# header_libs: ["bionic_libc_platform_headers"].
+source_set("bionic_libc_platform_headers_on_android") {
+}
+
+source_set("client_api") {
   if (perfetto_build_with_android) {
     cflags = [ "-DPERFETTO_ANDROID_ASYNC_SAFE_LOG" ]
   } else {
@@ -123,12 +170,21 @@
     "../../base",
     "../common:proc_utils",
   ]
-  sources = [ "client_ext.cc" ]
+  sources = [ "client_api.cc" ]
+}
+
+source_set("wrap_allocators") {
+  deps = [
+    "../../../gn:default_deps",
+    "../../base",
+  ]
+  sources = [ "wrap_allocators.cc" ]
 }
 
 source_set("wire_protocol") {
   public_deps = [ "../../../gn:libunwindstack" ]
   deps = [
+    ":bionic_libc_platform_headers_on_android",
     ":ring_buffer",
     "../../../gn:default_deps",
     "../../base",
@@ -183,12 +239,14 @@
     "../../../protos/perfetto/config/profiling:cpp",
     "../../base",
     "../../base:unix_socket",
+    "../../traced/probes/packages_list:packages_list_parser",
     "../../tracing/core",
     "../../tracing/ipc/producer",
     "../common:callstack_trie",
     "../common:interner",
     "../common:interning_output",
     "../common:proc_utils",
+    "../common:profiler_guardrails",
     "../common:unwind_support",
   ]
   public_deps = [
@@ -207,8 +265,6 @@
     "heapprofd_producer.h",
     "java_hprof_producer.cc",
     "java_hprof_producer.h",
-    "page_idle_checker.cc",
-    "page_idle_checker.h",
     "system_property.cc",
     "system_property.h",
     "unwinding.cc",
@@ -231,6 +287,7 @@
   sources = [
     "client.cc",
     "client.h",
+    "sampler.cc",
     "sampler.h",
   ]
 }
@@ -254,7 +311,6 @@
     "bookkeeping_unittest.cc",
     "client_unittest.cc",
     "heapprofd_producer_unittest.cc",
-    "page_idle_checker_unittest.cc",
     "parse_smaps_unittest.cc",
     "sampler_unittest.cc",
     "system_property_unittest.cc",
@@ -273,6 +329,7 @@
     "../../../gn:gtest_and_gmock",
     "../../../gn:libunwindstack",
     "../../../protos/perfetto/config/profiling:cpp",
+    "../../../protos/perfetto/trace/interned_data:cpp",
     "../../../protos/perfetto/trace/profiling:cpp",
     "../../../test:test_helper",
     "../../base",
diff --git a/src/profiling/memory/CHANGELOG.md b/src/profiling/memory/CHANGELOG.md
index 31a35bb..28be84f 100644
--- a/src/profiling/memory/CHANGELOG.md
+++ b/src/profiling/memory/CHANGELOG.md
@@ -1,4 +1,18 @@
-# Changes from Android 10
+# In Android 12 (upcoming)
+
+## New features
+* Support Custom Allocators. This allows developers to instrument their
+  applications to report memory allocations / frees that are not done
+  through the malloc-based system allocators.
+
+## Bugfixes
+* Fix problems with allocations done in signal handlers using SA_ONSTACK.
+* Fixed heapprofd for multi API. A 64-bit heapprofd service can now correctly
+  profile a 32-bit target.
+* Fixed a bug where specifying a sampling rate of 0 would crash the target
+  process.
+
+# In Android 11
 
 ## New features
 * Allow to specify whether profiling should only be done for existing processes
@@ -6,8 +20,16 @@
   `HeapprofdConfig`.
 * Allow to get the number of bytes that were allocated at a callstack but then
   not used.
-* Allow to dump the maximum, rather than at the time of the dump.
+* Allow to dump the maximum, rather than at the time of the dump using
+  `dump_at_max` in `HeapprofdConfig`.
+* Allow to specify timeout (`block_client_timeout_us`) when blocking mode is
+  used. This will tear down the profile if the client would be blocked for
+  longer than this.
+* Try to auto-detect if a process uses `vfork(2)` or `clone(2)` with
+  `CLONE_VM`. In Android 10, doing memory operations in a vfork-ed child (in
+  violation of POSIX) would tear down the parent's profiling session early.
 
 ## Bugfixes
 * Fixed heapprofd on x86.
 * Fixed issue with calloc being incorrectly sampled.
+* Remove benign `ERROR 2` bottom-most frame on ARM32.
diff --git a/src/profiling/memory/bookkeeping.cc b/src/profiling/memory/bookkeeping.cc
index 4da8291..3439db4 100644
--- a/src/profiling/memory/bookkeeping.cc
+++ b/src/profiling/memory/bookkeeping.cc
@@ -29,21 +29,26 @@
 namespace perfetto {
 namespace profiling {
 
-void HeapTracker::RecordMalloc(const std::vector<FrameData>& callstack,
-                               uint64_t address,
-                               uint64_t sample_size,
-                               uint64_t alloc_size,
-                               uint64_t sequence_number,
-                               uint64_t timestamp) {
+void HeapTracker::RecordMalloc(
+    const std::vector<unwindstack::FrameData>& callstack,
+    const std::vector<std::string>& build_ids,
+    uint64_t address,
+    uint64_t sample_size,
+    uint64_t alloc_size,
+    uint64_t sequence_number,
+    uint64_t timestamp) {
+  PERFETTO_CHECK(callstack.size() == build_ids.size());
   std::vector<Interned<Frame>> frames;
   frames.reserve(callstack.size());
-  for (const FrameData& loc : callstack) {
-    auto frame_it = frame_cache_.find(loc.frame.pc);
+  for (size_t i = 0; i < callstack.size(); ++i) {
+    const unwindstack::FrameData& loc = callstack[i];
+    const std::string& build_id = build_ids[i];
+    auto frame_it = frame_cache_.find(loc.pc);
     if (frame_it != frame_cache_.end()) {
       frames.emplace_back(frame_it->second);
     } else {
-      frames.emplace_back(callsites_->InternCodeLocation(loc));
-      frame_cache_.emplace(loc.frame.pc, frames.back());
+      frames.emplace_back(callsites_->InternCodeLocation(loc, build_id));
+      frame_cache_.emplace(loc.pc, frames.back());
     }
   }
 
@@ -130,9 +135,12 @@
   //  be treated as a no-op.
 }
 
-uint64_t HeapTracker::GetSizeForTesting(const std::vector<FrameData>& stack) {
+uint64_t HeapTracker::GetSizeForTesting(
+    const std::vector<unwindstack::FrameData>& stack,
+    std::vector<std::string> build_ids) {
   PERFETTO_DCHECK(!dump_at_max_mode_);
-  GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(stack);
+  GlobalCallstackTrie::Node* node =
+      callsites_->CreateCallsite(stack, build_ids);
   // Hack to make it go away again if it wasn't used before.
   // This is only good because this is used for testing only.
   GlobalCallstackTrie::IncrementNode(node);
@@ -145,9 +153,12 @@
   return alloc.value.totals.allocated - alloc.value.totals.freed;
 }
 
-uint64_t HeapTracker::GetMaxForTesting(const std::vector<FrameData>& stack) {
+uint64_t HeapTracker::GetMaxForTesting(
+    const std::vector<unwindstack::FrameData>& stack,
+    std::vector<std::string> build_ids) {
   PERFETTO_DCHECK(dump_at_max_mode_);
-  GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(stack);
+  GlobalCallstackTrie::Node* node =
+      callsites_->CreateCallsite(stack, build_ids);
   // Hack to make it go away again if it wasn't used before.
   // This is only good because this is used for testing only.
   GlobalCallstackTrie::IncrementNode(node);
@@ -160,6 +171,23 @@
   return alloc.value.retain_max.max;
 }
 
+uint64_t HeapTracker::GetMaxCountForTesting(
+    const std::vector<unwindstack::FrameData>& stack,
+    std::vector<std::string> build_ids) {
+  PERFETTO_DCHECK(dump_at_max_mode_);
+  GlobalCallstackTrie::Node* node =
+      callsites_->CreateCallsite(stack, build_ids);
+  // Hack to make it go away again if it wasn't used before.
+  // This is only good because this is used for testing only.
+  GlobalCallstackTrie::IncrementNode(node);
+  GlobalCallstackTrie::DecrementNode(node);
+  auto it = callstack_allocations_.find(node);
+  if (it == callstack_allocations_.end()) {
+    return 0;
+  }
+  const CallstackAllocations& alloc = it->second;
+  return alloc.value.retain_max.max_count;
+}
 
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/bookkeeping.h b/src/profiling/memory/bookkeeping.h
index 9568d4b..f6ab079 100644
--- a/src/profiling/memory/bookkeeping.h
+++ b/src/profiling/memory/bookkeeping.h
@@ -96,19 +96,22 @@
   struct CallstackMaxAllocations {
     uint64_t max;
     uint64_t cur;
+    uint64_t max_count;
+    uint64_t cur_count;
   };
   struct CallstackTotalAllocations {
     uint64_t allocated;
     uint64_t freed;
+    uint64_t allocation_count;
+    uint64_t free_count;
   };
 
   // Sum of all the allocations for a given callstack.
   struct CallstackAllocations {
-    CallstackAllocations(GlobalCallstackTrie::Node* n) : node(n) {}
+    explicit CallstackAllocations(GlobalCallstackTrie::Node* n) : node(n) {}
 
     uint64_t allocs = 0;
-    uint64_t allocation_count = 0;
-    uint64_t free_count = 0;
+
     union {
       CallstackMaxAllocations retain_max;
       CallstackTotalAllocations totals;
@@ -127,7 +130,8 @@
   explicit HeapTracker(GlobalCallstackTrie* callsites, bool dump_at_max_mode)
       : callsites_(callsites), dump_at_max_mode_(dump_at_max_mode) {}
 
-  void RecordMalloc(const std::vector<FrameData>& stack,
+  void RecordMalloc(const std::vector<unwindstack::FrameData>& callstack,
+                    const std::vector<std::string>& build_ids,
                     uint64_t address,
                     uint64_t sample_size,
                     uint64_t alloc_size,
@@ -145,7 +149,14 @@
       auto& it = it_and_alloc.first;
       uint64_t allocated = it_and_alloc.second;
       const CallstackAllocations& alloc = it->second;
-      if (alloc.allocs == 0 && alloc.allocation_count == allocated) {
+      // For non-dump-at-max, we need to check, even if there are still no
+      // allocations referencing this callstack, whether there were any
+      // allocations that happened but were freed again. If that was the case,
+      // we need to keep the callsite, because the next dump will indicate a
+      // different self_alloc and self_freed.
+      if (alloc.allocs == 0 &&
+          (dump_at_max_mode_ ||
+           alloc.value.totals.allocation_count == allocated)) {
         // TODO(fmayer): We could probably be smarter than throw away
         // our whole frames cache.
         ClearFrameCache();
@@ -160,7 +171,8 @@
       fn(alloc);
 
       if (alloc.allocs == 0)
-        dead_callstack_allocations_.emplace_back(it, alloc.allocation_count);
+        dead_callstack_allocations_.emplace_back(
+            it, !dump_at_max_mode_ ? alloc.value.totals.allocation_count : 0);
     }
   }
 
@@ -184,8 +196,14 @@
   uint64_t committed_timestamp() { return committed_timestamp_; }
   uint64_t max_timestamp() { return max_timestamp_; }
 
-  uint64_t GetSizeForTesting(const std::vector<FrameData>& stack);
-  uint64_t GetMaxForTesting(const std::vector<FrameData>& stack);
+  uint64_t GetSizeForTesting(const std::vector<unwindstack::FrameData>& stack,
+                             std::vector<std::string> build_ids);
+  uint64_t GetMaxForTesting(const std::vector<unwindstack::FrameData>& stack,
+                            std::vector<std::string> build_ids);
+  uint64_t GetMaxCountForTesting(
+      const std::vector<unwindstack::FrameData>& stack,
+      std::vector<std::string> build_ids);
+
   uint64_t GetTimestampForTesting() { return committed_timestamp_; }
 
  private:
@@ -263,19 +281,21 @@
                        const PendingOperation& operation);
 
   void AddToCallstackAllocations(uint64_t ts, const Allocation& alloc) {
-    alloc.callstack_allocations()->allocation_count++;
     if (dump_at_max_mode_) {
       current_unfreed_ += alloc.sample_size;
       alloc.callstack_allocations()->value.retain_max.cur += alloc.sample_size;
+      alloc.callstack_allocations()->value.retain_max.cur_count++;
 
       if (current_unfreed_ <= max_unfreed_)
         return;
 
       if (max_sequence_number_ == alloc.sequence_number - 1) {
+        // We know the only CallstackAllocation that has max != cur is the
+        // one we just updated.
         alloc.callstack_allocations()->value.retain_max.max =
-            // We know the only CallstackAllocation that has max != cur is the
-            // one we just updated.
             alloc.callstack_allocations()->value.retain_max.cur;
+        alloc.callstack_allocations()->value.retain_max.max_count =
+            alloc.callstack_allocations()->value.retain_max.cur_count;
       } else {
         for (auto& p : callstack_allocations_) {
           // We need to reset max = cur for every CallstackAllocation, as we
@@ -291,16 +311,18 @@
     } else {
       alloc.callstack_allocations()->value.totals.allocated +=
           alloc.sample_size;
+      alloc.callstack_allocations()->value.totals.allocation_count++;
     }
   }
 
   void SubtractFromCallstackAllocations(const Allocation& alloc) {
-    alloc.callstack_allocations()->free_count++;
     if (dump_at_max_mode_) {
       current_unfreed_ -= alloc.sample_size;
       alloc.callstack_allocations()->value.retain_max.cur -= alloc.sample_size;
+      alloc.callstack_allocations()->value.retain_max.cur_count--;
     } else {
       alloc.callstack_allocations()->value.totals.freed += alloc.sample_size;
+      alloc.callstack_allocations()->value.totals.free_count++;
     }
   }
 
diff --git a/src/profiling/memory/bookkeeping_dump.cc b/src/profiling/memory/bookkeeping_dump.cc
index 514424b..7ce4a8c 100644
--- a/src/profiling/memory/bookkeeping_dump.cc
+++ b/src/profiling/memory/bookkeeping_dump.cc
@@ -58,16 +58,14 @@
   sample->set_callstack_id(alloc.node->id());
   if (dump_at_max_mode) {
     sample->set_self_max(alloc.value.retain_max.max);
+    sample->set_self_max_count(alloc.value.retain_max.max_count);
   } else {
     sample->set_self_allocated(alloc.value.totals.allocated);
     sample->set_self_freed(alloc.value.totals.freed);
-  }
-  sample->set_alloc_count(alloc.allocation_count);
-  sample->set_free_count(alloc.free_count);
 
-  auto it = current_process_idle_allocs_.find(alloc.node->id());
-  if (it != current_process_idle_allocs_.end())
-    sample->set_self_idle(it->second);
+    sample->set_alloc_count(alloc.value.totals.allocation_count);
+    sample->set_free_count(alloc.value.totals.free_count);
+  }
 }
 
 void DumpState::DumpCallstacks(GlobalCallstackTrie* callsites) {
@@ -87,10 +85,6 @@
   MakeProfilePacket();
 }
 
-void DumpState::AddIdleBytes(uint64_t callstack_id, uint64_t bytes) {
-  current_process_idle_allocs_[callstack_id] += bytes;
-}
-
 ProfilePacket::ProcessHeapSamples* DumpState::GetCurrentProcessHeapSamples() {
   if (currently_written() > kPacketSizeThreshold) {
     if (current_profile_packet_)
diff --git a/src/profiling/memory/bookkeeping_dump.h b/src/profiling/memory/bookkeeping_dump.h
index bf19133..9c37676 100644
--- a/src/profiling/memory/bookkeeping_dump.h
+++ b/src/profiling/memory/bookkeeping_dump.h
@@ -56,8 +56,6 @@
   DumpState(DumpState&&) = delete;
   DumpState& operator=(DumpState&&) = delete;
 
-  void AddIdleBytes(uint64_t callstack_id, uint64_t bytes);
-
   void WriteAllocation(const HeapTracker::CallstackAllocations& alloc,
                        bool dump_at_max_mode);
   void DumpCallstacks(GlobalCallstackTrie* callsites);
@@ -110,7 +108,6 @@
       current_process_heap_samples_ = nullptr;
   std::function<void(protos::pbzero::ProfilePacket::ProcessHeapSamples*)>
       current_process_fill_header_;
-  std::map<uint64_t /* callstack_id */, uint64_t> current_process_idle_allocs_;
 
   uint64_t last_written_ = 0;
 };
diff --git a/src/profiling/memory/bookkeeping_unittest.cc b/src/profiling/memory/bookkeeping_unittest.cc
index b3d6a4e..a242786 100644
--- a/src/profiling/memory/bookkeeping_unittest.cc
+++ b/src/profiling/memory/bookkeeping_unittest.cc
@@ -25,43 +25,47 @@
 using ::testing::AnyOf;
 using ::testing::Eq;
 
-std::vector<FrameData> stack() {
-  std::vector<FrameData> res;
+std::vector<unwindstack::FrameData> stack() {
+  std::vector<unwindstack::FrameData> res;
 
   unwindstack::FrameData data{};
   data.function_name = "fun1";
   data.map_name = "map1";
   data.pc = 1;
-  res.emplace_back(std::move(data), "dummy_buildid");
+  res.emplace_back(std::move(data));
   data = {};
   data.function_name = "fun2";
   data.map_name = "map2";
   data.pc = 2;
-  res.emplace_back(std::move(data), "dummy_buildid");
+  res.emplace_back(std::move(data));
   return res;
 }
 
-std::vector<FrameData> stack2() {
-  std::vector<FrameData> res;
+std::vector<unwindstack::FrameData> stack2() {
+  std::vector<unwindstack::FrameData> res;
   unwindstack::FrameData data{};
   data.function_name = "fun1";
   data.map_name = "map1";
   data.pc = 1;
-  res.emplace_back(std::move(data), "dummy_buildid");
+  res.emplace_back(std::move(data));
   data = {};
   data.function_name = "fun3";
   data.map_name = "map3";
   data.pc = 3;
-  res.emplace_back(std::move(data), "dummy_buildid");
+  res.emplace_back(std::move(data));
   return res;
 }
 
+std::vector<std::string> DummyBuildIds(size_t n) {
+  return std::vector<std::string>(n, "dummy_buildid");
+}
+
 TEST(BookkeepingTest, EmptyStack) {
   uint64_t sequence_number = 1;
   GlobalCallstackTrie c;
   HeapTracker hd(&c, false);
 
-  hd.RecordMalloc({}, 0x1, 5, 5, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc({}, {}, 0x1, 5, 5, sequence_number, 100 * sequence_number);
   sequence_number++;
   hd.RecordFree(0x1, sequence_number, 100 * sequence_number);
   hd.GetCallstackAllocations([](const HeapTracker::CallstackAllocations&) {});
@@ -72,9 +76,11 @@
   GlobalCallstackTrie c;
   HeapTracker hd(&c, false);
 
-  hd.RecordMalloc(stack(), 1, 5, 5, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 1, 5, 5,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  hd.RecordMalloc(stack2(), 1, 2, 2, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 1, 2, 2,
+                  sequence_number, 100 * sequence_number);
 
   // Call GetCallstackAllocations twice to force GC of old CallstackAllocations.
   hd.GetCallstackAllocations([](const HeapTracker::CallstackAllocations&) {});
@@ -86,22 +92,24 @@
   GlobalCallstackTrie c;
   HeapTracker hd(&c, false);
 
-  hd.RecordMalloc(stack(), 0x1, 5, 5, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x1, 5, 5,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  hd.RecordMalloc(stack2(), 0x2, 2, 2, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 0x2, 2, 2,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  ASSERT_EQ(hd.GetSizeForTesting(stack()), 5u);
-  ASSERT_EQ(hd.GetSizeForTesting(stack2()), 2u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 5u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack2(), DummyBuildIds(stack2().size())), 2u);
   ASSERT_EQ(hd.GetTimestampForTesting(), 100 * (sequence_number - 1));
   hd.RecordFree(0x2, sequence_number, 100 * sequence_number);
   sequence_number++;
-  ASSERT_EQ(hd.GetSizeForTesting(stack()), 5u);
-  ASSERT_EQ(hd.GetSizeForTesting(stack2()), 0u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 5u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack2(), DummyBuildIds(stack2().size())), 0u);
   ASSERT_EQ(hd.GetTimestampForTesting(), 100 * (sequence_number - 1));
   hd.RecordFree(0x1, sequence_number, 100 * sequence_number);
   sequence_number++;
-  ASSERT_EQ(hd.GetSizeForTesting(stack()), 0u);
-  ASSERT_EQ(hd.GetSizeForTesting(stack2()), 0u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 0u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack2(), DummyBuildIds(stack2().size())), 0u);
   ASSERT_EQ(hd.GetTimestampForTesting(), 100 * (sequence_number - 1));
 }
 
@@ -110,17 +118,25 @@
   GlobalCallstackTrie c;
   HeapTracker hd(&c, true);
 
-  hd.RecordMalloc(stack(), 0x1, 5, 5, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x1, 5, 5,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  hd.RecordMalloc(stack2(), 0x2, 2, 2, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 0x2, 2, 2,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
   hd.RecordFree(0x2, sequence_number, 100 * sequence_number);
   sequence_number++;
   hd.RecordFree(0x1, sequence_number, 100 * sequence_number);
   sequence_number++;
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 0x2, 1, 2,
+                  sequence_number, 100 * sequence_number);
   ASSERT_EQ(hd.max_timestamp(), 200u);
-  ASSERT_EQ(hd.GetMaxForTesting(stack()), 5u);
-  ASSERT_EQ(hd.GetMaxForTesting(stack2()), 2u);
+  ASSERT_EQ(hd.GetMaxForTesting(stack(), DummyBuildIds(stack().size())), 5u);
+  ASSERT_EQ(hd.GetMaxForTesting(stack2(), DummyBuildIds(stack2().size())), 2u);
+  ASSERT_EQ(hd.GetMaxCountForTesting(stack(), DummyBuildIds(stack().size())),
+            1u);
+  ASSERT_EQ(hd.GetMaxCountForTesting(stack2(), DummyBuildIds(stack2().size())),
+            1u);
 }
 
 TEST(BookkeepingTest, TwoHeapTrackers) {
@@ -130,15 +146,17 @@
   {
     HeapTracker hd2(&c, false);
 
-    hd.RecordMalloc(stack(), 0x1, 5, 5, sequence_number, 100 * sequence_number);
-    hd2.RecordMalloc(stack(), 0x2, 2, 2, sequence_number,
-                     100 * sequence_number);
+    hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x1, 5, 5,
+                    sequence_number, 100 * sequence_number);
+    hd2.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x2, 2, 2,
+                     sequence_number, 100 * sequence_number);
     sequence_number++;
-    ASSERT_EQ(hd2.GetSizeForTesting(stack()), 2u);
-    ASSERT_EQ(hd.GetSizeForTesting(stack()), 5u);
+    ASSERT_EQ(hd2.GetSizeForTesting(stack(), DummyBuildIds(stack().size())),
+              2u);
+    ASSERT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 5u);
     ASSERT_EQ(hd.GetTimestampForTesting(), 100 * (sequence_number - 1));
   }
-  ASSERT_EQ(hd.GetSizeForTesting(stack()), 5u);
+  ASSERT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 5u);
 }
 
 TEST(BookkeepingTest, ReplaceAlloc) {
@@ -146,12 +164,14 @@
   GlobalCallstackTrie c;
   HeapTracker hd(&c, false);
 
-  hd.RecordMalloc(stack(), 0x1, 5, 5, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x1, 5, 5,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  hd.RecordMalloc(stack2(), 0x1, 2, 2, sequence_number, 100 * sequence_number);
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 0x1, 2, 2,
+                  sequence_number, 100 * sequence_number);
   sequence_number++;
-  EXPECT_EQ(hd.GetSizeForTesting(stack()), 0u);
-  EXPECT_EQ(hd.GetSizeForTesting(stack2()), 2u);
+  EXPECT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 0u);
+  EXPECT_EQ(hd.GetSizeForTesting(stack2(), DummyBuildIds(stack2().size())), 2u);
   ASSERT_EQ(hd.GetTimestampForTesting(), 100 * (sequence_number - 1));
 }
 
@@ -159,10 +179,10 @@
   GlobalCallstackTrie c;
   HeapTracker hd(&c, false);
 
-  hd.RecordMalloc(stack(), 0x1, 5, 5, 2, 2);
-  hd.RecordMalloc(stack2(), 0x1, 2, 2, 1, 1);
-  EXPECT_EQ(hd.GetSizeForTesting(stack()), 5u);
-  EXPECT_EQ(hd.GetSizeForTesting(stack2()), 0u);
+  hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), 0x1, 5, 5, 2, 2);
+  hd.RecordMalloc(stack2(), DummyBuildIds(stack2().size()), 0x1, 2, 2, 1, 1);
+  EXPECT_EQ(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())), 5u);
+  EXPECT_EQ(hd.GetSizeForTesting(stack2(), DummyBuildIds(stack2().size())), 0u);
 }
 
 TEST(BookkeepingTest, ManyAllocations) {
@@ -179,16 +199,21 @@
     }
 
     uint64_t addr = sequence_number;
-    hd.RecordMalloc(stack(), addr, 5, 5, sequence_number, sequence_number);
+    hd.RecordMalloc(stack(), DummyBuildIds(stack().size()), addr, 5, 5,
+                    sequence_number, sequence_number);
     sequence_number++;
     batch_frees.emplace_back(addr, sequence_number++);
-    ASSERT_THAT(hd.GetSizeForTesting(stack()), AnyOf(Eq(0u), Eq(5u)));
+    ASSERT_THAT(hd.GetSizeForTesting(stack(), DummyBuildIds(stack().size())),
+                AnyOf(Eq(0u), Eq(5u)));
   }
 }
 
 TEST(BookkeepingTest, ArbitraryOrder) {
-  std::vector<FrameData> s = stack();
-  std::vector<FrameData> s2 = stack2();
+  std::vector<unwindstack::FrameData> s = stack();
+  std::vector<unwindstack::FrameData> s2 = stack2();
+
+  std::vector<std::string> s_b = DummyBuildIds(s.size());
+  std::vector<std::string> s2_b = DummyBuildIds(s2.size());
 
   enum OperationType { kAlloc, kFree, kDump };
 
@@ -197,21 +222,22 @@
     OperationType type;
     uint64_t address;
     uint64_t bytes;                       // 0 for free
-    const std::vector<FrameData>* stack;  // nullptr for free
+    const std::vector<unwindstack::FrameData>* stack;  // nullptr for free
+    const std::vector<std::string>* build_ids;         // nullptr for free
 
     // For std::next_permutation.
     bool operator<(const Operation& other) const {
       return sequence_number < other.sequence_number;
     }
   } operations[] = {
-      {1, kAlloc, 0x1, 5, &s},       //
-      {2, kAlloc, 0x1, 10, &s2},     //
-      {3, kFree, 0x01, 0, nullptr},  //
-      {4, kFree, 0x02, 0, nullptr},  //
-      {5, kFree, 0x03, 0, nullptr},  //
-      {6, kAlloc, 0x3, 2, &s},       //
-      {7, kAlloc, 0x4, 3, &s2},      //
-      {0, kDump, 0x00, 0, nullptr},  //
+      {1, kAlloc, 0x1, 5, &s, &s_b},          //
+      {2, kAlloc, 0x1, 10, &s2, &s2_b},       //
+      {3, kFree, 0x01, 0, nullptr, nullptr},  //
+      {4, kFree, 0x02, 0, nullptr, nullptr},  //
+      {5, kFree, 0x03, 0, nullptr, nullptr},  //
+      {6, kAlloc, 0x3, 2, &s, &s_b},          //
+      {7, kAlloc, 0x4, 3, &s2, &s2_b},        //
+      {0, kDump, 0x00, 0, nullptr, nullptr},  //
   };
 
   uint64_t s_size = 2;
@@ -228,16 +254,17 @@
         hd.RecordFree(operation.address, operation.sequence_number,
                       100 * operation.sequence_number);
       } else if (operation.type == OperationType::kAlloc) {
-        hd.RecordMalloc(*operation.stack, operation.address, operation.bytes,
-                        operation.bytes, operation.sequence_number,
+        hd.RecordMalloc(*operation.stack, DummyBuildIds(stack().size()),
+                        operation.address, operation.bytes, operation.bytes,
+                        operation.sequence_number,
                         100 * operation.sequence_number);
       } else {
         hd.GetCallstackAllocations(
             [](const HeapTracker::CallstackAllocations&) {});
       }
     }
-    ASSERT_EQ(hd.GetSizeForTesting(s), s_size);
-    ASSERT_EQ(hd.GetSizeForTesting(s2), s2_size);
+    ASSERT_EQ(hd.GetSizeForTesting(s, s_b), s_size);
+    ASSERT_EQ(hd.GetSizeForTesting(s2, s2_b), s2_size);
   } while (std::next_permutation(std::begin(operations), std::end(operations)));
 }
 
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index 0d09dbe..fc259a2 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -17,10 +17,12 @@
 #include "src/profiling/memory/client.h"
 
 #include <inttypes.h>
+#include <signal.h>
 #include <sys/prctl.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
+
 #include <unwindstack/MachineArm.h>
 #include <unwindstack/MachineArm64.h>
 #include <unwindstack/MachineMips.h>
@@ -34,9 +36,11 @@
 #include <atomic>
 #include <new>
 
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/thread_utils.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
@@ -55,36 +59,15 @@
   return getpid() == base::GetThreadId();
 }
 
-// The implementation of pthread_getattr_np for the main thread uses malloc,
-// so we cannot use it in GetStackBase, which we use inside of RecordMalloc
-// (which is called from malloc). We would re-enter malloc if we used it.
-//
-// This is why we find the stack base for the main-thread when constructing
-// the client and remember it.
-char* FindMainThreadStack() {
-  base::ScopedFstream maps(fopen("/proc/self/maps", "r"));
-  if (!maps) {
-    return nullptr;
-  }
-  while (!feof(*maps)) {
-    char line[1024];
-    char* data = fgets(line, sizeof(line), *maps);
-    if (data != nullptr && strstr(data, "[stack]")) {
-      char* sep = strstr(data, "-");
-      if (sep == nullptr)
-        continue;
-      sep++;
-      return reinterpret_cast<char*>(strtoll(sep, nullptr, 16));
-    }
-  }
-  return nullptr;
-}
-
 int UnsetDumpable(int) {
   prctl(PR_SET_DUMPABLE, 0);
   return 0;
 }
 
+bool Contained(const StackRange& base, const char* ptr) {
+  return (ptr >= base.begin && ptr < base.end);
+}
+
 }  // namespace
 
 uint64_t GetMaxTries(const ClientConfiguration& client_config) {
@@ -96,10 +79,10 @@
       1ul, client_config.block_client_timeout_us / kResendBackoffUs);
 }
 
-const char* GetThreadStackBase() {
+StackRange GetThreadStackRange() {
   pthread_attr_t attr;
   if (pthread_getattr_np(pthread_self(), &attr) != 0)
-    return nullptr;
+    return {nullptr, nullptr};
   base::ScopedResource<pthread_attr_t*, pthread_attr_destroy, nullptr> cleanup(
       &attr);
 
@@ -107,8 +90,51 @@
   size_t stacksize;
   if (pthread_attr_getstack(&attr, reinterpret_cast<void**>(&stackaddr),
                             &stacksize) != 0)
-    return nullptr;
-  return stackaddr + stacksize;
+    return {nullptr, nullptr};
+  return {stackaddr, stackaddr + stacksize};
+}
+
+StackRange GetSigAltStackRange() {
+  stack_t altstack;
+
+  if (sigaltstack(nullptr, &altstack) == -1) {
+    PERFETTO_PLOG("sigaltstack");
+    return {nullptr, nullptr};
+  }
+
+  if ((altstack.ss_flags & SS_ONSTACK) == 0) {
+    return {nullptr, nullptr};
+  }
+
+  return {static_cast<char*>(altstack.ss_sp),
+          static_cast<char*>(altstack.ss_sp) + altstack.ss_size};
+}
+
+// The implementation of pthread_getattr_np for the main thread uses malloc,
+// so we cannot use it in GetStackEnd, which we use inside of RecordMalloc
+// (which is called from malloc). We would re-enter malloc if we used it.
+//
+// This is why we find the stack base for the main-thread when constructing
+// the client and remember it.
+StackRange GetMainThreadStackRange() {
+  base::ScopedFstream maps(fopen("/proc/self/maps", "re"));
+  if (!maps) {
+    return {nullptr, nullptr};
+  }
+  while (!feof(*maps)) {
+    char line[1024];
+    char* data = fgets(line, sizeof(line), *maps);
+    if (data != nullptr && strstr(data, "[stack]")) {
+      char* sep = strstr(data, "-");
+      if (sep == nullptr)
+        continue;
+
+      char* min = reinterpret_cast<char*>(strtoll(data, nullptr, 16));
+      char* max = reinterpret_cast<char*>(strtoll(sep + 1, nullptr, 16));
+      return {min, max};
+    }
+  }
+  return {nullptr, nullptr};
 }
 
 // static
@@ -169,19 +195,12 @@
     return nullptr;
   }
 
-  base::ScopedFile page_idle(base::OpenFile("/proc/self/page_idle", O_RDWR));
-  if (!page_idle) {
-    PERFETTO_DLOG("Failed to open /proc/self/page_idle. Continuing.");
-    num_send_fds = kHandshakeSize - 1;
-  }
-
   // Restore original dumpability value if we overrode it.
   unset_dumpable.reset();
 
   int fds[kHandshakeSize];
   fds[kHandshakeMaps] = *maps;
   fds[kHandshakeMem] = *mem;
-  fds[kHandshakePageIdle] = *page_idle;
 
   // Send an empty record to transfer fds for /proc/self/maps and
   // /proc/self/mem.
@@ -225,27 +244,22 @@
     return nullptr;
   }
 
-  PERFETTO_DCHECK(client_config.interval >= 1);
   sock.SetBlocking(false);
-  Sampler sampler{client_config.interval};
   // note: the shared_ptr will retain a copy of the unhooked_allocator
   return std::allocate_shared<Client>(unhooked_allocator, std::move(sock),
                                       client_config, std::move(shmem.value()),
-                                      std::move(sampler), getpid(),
-                                      FindMainThreadStack());
+                                      getpid(), GetMainThreadStackRange());
 }
 
 Client::Client(base::UnixSocketRaw sock,
                ClientConfiguration client_config,
                SharedRingBuffer shmem,
-               Sampler sampler,
                pid_t pid_at_creation,
-               const char* main_thread_stack_base)
+               StackRange main_thread_stack_range)
     : client_config_(client_config),
       max_shmem_tries_(GetMaxTries(client_config_)),
-      sampler_(std::move(sampler)),
       sock_(std::move(sock)),
-      main_thread_stack_base_(main_thread_stack_base),
+      main_thread_stack_range_(main_thread_stack_range),
       shmem_(std::move(shmem)),
       pid_at_creation_(pid_at_creation) {}
 
@@ -263,15 +277,29 @@
     close(fd);
 }
 
-const char* Client::GetStackBase() {
-  if (IsMainThread()) {
-    if (!main_thread_stack_base_)
-      // Because pthread_attr_getstack reads and parses /proc/self/maps and
-      // /proc/self/stat, we have to cache the result here.
-      main_thread_stack_base_ = GetThreadStackBase();
-    return main_thread_stack_base_;
+const char* Client::GetStackEnd(const char* stackptr) {
+  StackRange thread_stack_range;
+  bool is_main_thread = IsMainThread();
+  if (is_main_thread) {
+    thread_stack_range = main_thread_stack_range_;
+  } else {
+    thread_stack_range = GetThreadStackRange();
   }
-  return GetThreadStackBase();
+  if (Contained(thread_stack_range, stackptr)) {
+    return thread_stack_range.end;
+  }
+  StackRange sigalt_stack_range = GetSigAltStackRange();
+  if (Contained(sigalt_stack_range, stackptr)) {
+    return sigalt_stack_range.end;
+  }
+  // The main thread might have expanded since we read its bounds. We now know
+  // it is not the sigaltstack, so it has to be the main stack.
+  // TODO(fmayer): We should reparse maps here, because now we will keep
+  //               hitting the slow-path that calls the sigaltstack syscall.
+  if (is_main_thread && stackptr < thread_stack_range.end) {
+    return thread_stack_range.end;
+  }
+  return nullptr;
 }
 
 // Best-effort detection of whether we're continuing work in a forked child of
@@ -323,13 +351,13 @@
 //
 //               +------------+
 //               |SendWireMsg |
-// stacktop +--> +------------+ 0x1000
+// stackptr +--> +------------+ 0x1000
 //               |RecordMalloc|    +
 //               +------------+    |
 //               | malloc     |    |
 //               +------------+    |
 //               |  main      |    v
-// stackbase +-> +------------+ 0xffff
+// stackend  +-> +------------+ 0xffff
 bool Client::RecordMalloc(uint32_t heap_id,
                           uint64_t sample_size,
                           uint64_t alloc_size,
@@ -339,20 +367,18 @@
   }
 
   AllocMetadata metadata;
-  const char* stackbase = GetStackBase();
-  const char* stacktop = reinterpret_cast<char*>(__builtin_frame_address(0));
+  const char* stackptr = reinterpret_cast<char*>(__builtin_frame_address(0));
   unwindstack::AsmGetRegs(metadata.register_data);
-
-  if (PERFETTO_UNLIKELY(stackbase < stacktop)) {
-    PERFETTO_DFATAL_OR_ELOG("Stackbase >= stacktop.");
+  const char* stackend = GetStackEnd(stackptr);
+  if (!stackend) {
+    PERFETTO_ELOG("Failed to find stackend.");
     return false;
   }
-
-  uint64_t stack_size = static_cast<uint64_t>(stackbase - stacktop);
+  uint64_t stack_size = static_cast<uint64_t>(stackend - stackptr);
   metadata.sample_size = sample_size;
   metadata.alloc_size = alloc_size;
   metadata.alloc_address = alloc_address;
-  metadata.stack_pointer = reinterpret_cast<uint64_t>(stacktop);
+  metadata.stack_pointer = reinterpret_cast<uint64_t>(stackptr);
   metadata.arch = unwindstack::Regs::CurrentArch();
   metadata.sequence_number =
       1 + sequence_number_[heap_id].fetch_add(1, std::memory_order_acq_rel);
@@ -369,18 +395,22 @@
   WireMessage msg{};
   msg.record_type = RecordType::Malloc;
   msg.alloc_header = &metadata;
-  msg.payload = const_cast<char*>(stacktop);
+  msg.payload = const_cast<char*>(stackptr);
   msg.payload_size = static_cast<size_t>(stack_size);
 
   if (SendWireMessageWithRetriesIfBlocking(msg) == -1)
     return false;
 
+  if (!shmem_.GetAndResetReaderPaused())
+    return true;
   return SendControlSocketByte();
 }
 
 int64_t Client::SendWireMessageWithRetriesIfBlocking(const WireMessage& msg) {
   for (uint64_t i = 0;
        max_shmem_tries_ == kInfiniteTries || i < max_shmem_tries_; ++i) {
+    if (shmem_.shutting_down())
+      return -1;
     int64_t res = SendWireMessage(&shmem_, msg);
     if (PERFETTO_LIKELY(res >= 0))
       return res;
@@ -422,6 +452,22 @@
   return true;
 }
 
+bool Client::RecordHeapName(uint32_t heap_id, const char* heap_name) {
+  if (PERFETTO_UNLIKELY(IsPostFork())) {
+    return postfork_return_value_;
+  }
+
+  HeapName hnr;
+  hnr.heap_id = heap_id;
+  strncpy(&hnr.heap_name[0], heap_name, sizeof(hnr.heap_name));
+  hnr.heap_name[sizeof(hnr.heap_name) - 1] = '\0';
+
+  WireMessage msg = {};
+  msg.record_type = RecordType::HeapName;
+  msg.heap_name_header = &hnr;
+  return SendWireMessageWithRetriesIfBlocking(msg);
+}
+
 bool Client::IsConnected() {
   PERFETTO_DCHECK(!sock_.IsBlocking());
   char buf[1];
@@ -442,7 +488,11 @@
   // is how the service signals the tracing session was torn down.
   if (sock_.Send(kSingleByte, sizeof(kSingleByte)) == -1 &&
       !base::IsAgain(errno)) {
-    PERFETTO_PLOG("Failed to send control socket byte.");
+    if (shmem_.shutting_down()) {
+      PERFETTO_LOG("Profiling session ended.");
+    } else {
+      PERFETTO_PLOG("Failed to send control socket byte.");
+    }
     return false;
   }
   return true;
diff --git a/src/profiling/memory/client.h b/src/profiling/memory/client.h
index f029a51..5527421 100644
--- a/src/profiling/memory/client.h
+++ b/src/profiling/memory/client.h
@@ -35,12 +35,21 @@
 namespace perfetto {
 namespace profiling {
 
-uint64_t GetMaxTries(const ClientConfiguration& client_config);
-const char* GetThreadStackBase();
+struct StackRange {
+  const char* begin;
+  // One past the highest address part of the stack.
+  const char* end;
+};
+
+StackRange GetThreadStackRange();
+StackRange GetSigAltStackRange();
+StackRange GetMainThreadStackRange();
 
 constexpr uint64_t kInfiniteTries = 0;
 constexpr uint32_t kClientSockTimeoutMs = 1000;
 
+uint64_t GetMaxTries(const ClientConfiguration& client_config);
+
 // Profiling client, used to sample and record the malloc/free family of calls,
 // and communicate the necessary state to a separate profiling daemon process.
 //
@@ -76,15 +85,10 @@
   // Add address to buffer of deallocations. Flushes the buffer if necessary.
   bool RecordFree(uint32_t heap_id,
                   uint64_t alloc_address) PERFETTO_WARN_UNUSED_RESULT;
+  bool RecordHeapName(uint32_t heap_id, const char* heap_name);
 
-  // Returns the number of bytes to assign to an allocation with the given
-  // |alloc_size|, based on the current sampling rate. A return value of zero
-  // means that the allocation should not be recorded. Not idempotent, each
-  // invocation mutates the sampler state.
-  //
-  // Not thread-safe.
-  size_t GetSampleSizeLocked(size_t alloc_size) {
-    return sampler_.SampleSize(alloc_size);
+  void AddClientSpinlockBlockedUs(size_t n) {
+    shmem_.AddClientSpinlockBlockedUs(n);
   }
 
   // Public for std::allocate_shared. Use CreateAndHandshake() to create
@@ -92,9 +96,8 @@
   Client(base::UnixSocketRaw sock,
          ClientConfiguration client_config,
          SharedRingBuffer shmem,
-         Sampler sampler,
          pid_t pid_at_creation,
-         const char* main_thread_stack_base);
+         StackRange main_thread_stack_range);
 
   ~Client();
 
@@ -103,7 +106,7 @@
   bool IsConnected();
 
  private:
-  const char* GetStackBase();
+  const char* GetStackEnd(const char* stacktop);
   bool SendControlSocketByte() PERFETTO_WARN_UNUSED_RESULT;
   int64_t SendWireMessageWithRetriesIfBlocking(const WireMessage&)
       PERFETTO_WARN_UNUSED_RESULT;
@@ -113,11 +116,9 @@
 
   ClientConfiguration client_config_;
   uint64_t max_shmem_tries_;
-  // sampler_ operations are not thread-safe.
-  Sampler sampler_;
   base::UnixSocketRaw sock_;
 
-  const char* main_thread_stack_base_{nullptr};
+  StackRange main_thread_stack_range_{nullptr, nullptr};
   std::atomic<uint64_t>
       sequence_number_[base::ArraySize(ClientConfiguration{}.heaps)] = {};
   SharedRingBuffer shmem_;
diff --git a/src/profiling/memory/client_ext.cc b/src/profiling/memory/client_api.cc
similarity index 66%
rename from src/profiling/memory/client_ext.cc
rename to src/profiling/memory/client_api.cc
index d87aa4e..72923f6 100644
--- a/src/profiling/memory/client_ext.cc
+++ b/src/profiling/memory/client_api.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
 
 #include <inttypes.h>
 #include <malloc.h>
@@ -38,7 +38,7 @@
 
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/memory/client.h"
-#include "src/profiling/memory/client_ext_factory.h"
+#include "src/profiling/memory/client_api_factory.h"
 #include "src/profiling/memory/scoped_spinlock.h"
 #include "src/profiling/memory/unhooked_allocator.h"
 #include "src/profiling/memory/wire_protocol.h"
@@ -46,6 +46,26 @@
 using perfetto::profiling::ScopedSpinlock;
 using perfetto::profiling::UnhookedAllocator;
 
+struct AHeapInfo {
+  // Fields set by user.
+  char heap_name[HEAPPROFD_HEAP_NAME_SZ];
+  void (*enabled_callback)(void*, const AHeapProfileEnableCallbackInfo*);
+  void (*disabled_callback)(void*, const AHeapProfileDisableCallbackInfo*);
+  void* enabled_callback_data;
+  void* disabled_callback_data;
+
+  // Internal fields.
+  perfetto::profiling::Sampler sampler;
+  std::atomic<bool> ready;
+  std::atomic<bool> enabled;
+};
+
+struct AHeapProfileEnableCallbackInfo {
+  uint64_t sampling_interval;
+};
+
+struct AHeapProfileDisableCallbackInfo {};
+
 namespace {
 #if defined(__GLIBC__)
 const char* getprogname() {
@@ -90,16 +110,9 @@
 
 constexpr auto kMinHeapId = 1;
 
-struct HeapprofdHeapInfoInternal {
-  HeapprofdHeapInfo info;
-  std::atomic<bool> ready;
-  std::atomic<bool> enabled;
-  std::atomic<uint32_t> service_heap_id;
-};
+AHeapInfo g_heaps[256];
 
-HeapprofdHeapInfoInternal g_heaps[256];
-
-HeapprofdHeapInfoInternal& GetHeap(uint32_t id) {
+AHeapInfo& GetHeap(uint32_t id) {
   return g_heaps[id];
 }
 
@@ -113,7 +126,6 @@
 
 std::atomic<uint32_t> g_next_heap_id{kMinHeapId};
 
-
 // Called only if |g_client_lock| acquisition fails, which shouldn't happen
 // unless we're in a completely unexpected state (which we won't know how to
 // recover from). Tries to abort (SIGABRT) the whole process to serve as an
@@ -134,13 +146,15 @@
 
 void DisableAllHeaps() {
   for (uint32_t i = kMinHeapId; i < g_next_heap_id.load(); ++i) {
-    HeapprofdHeapInfoInternal& heap = GetHeap(i);
-    if (!heap.ready.load(std::memory_order_acquire))
+    AHeapInfo& info = GetHeap(i);
+    if (!info.ready.load(std::memory_order_acquire))
       continue;
-    if (heap.enabled.load(std::memory_order_acquire)) {
-      heap.enabled.store(false, std::memory_order_release);
-      if (heap.info.callback)
-        heap.info.callback(false);
+    if (info.enabled.load(std::memory_order_acquire)) {
+      info.enabled.store(false, std::memory_order_release);
+      if (info.disabled_callback) {
+        AHeapProfileDisableCallbackInfo disable_info;
+        info.disabled_callback(info.disabled_callback_data, &disable_info);
+      }
     }
   }
 }
@@ -209,33 +223,69 @@
 
 }  // namespace
 
-__attribute__((visibility("default"))) uint32_t heapprofd_register_heap(
-    const HeapprofdHeapInfo* info,
-    size_t n) {
-  // For backwards compatibility, we handle HeapprofdHeapInfo that are shorter
-  // than the current one (and assume all new fields are unset). If someone
-  // calls us with a *newer* HeapprofdHeapInfo than this version of the library
-  // understands, error out.
-  if (n > sizeof(HeapprofdHeapInfo)) {
-    return 0;
+__attribute__((visibility("default"))) uint64_t
+AHeapProfileEnableCallbackInfo_getSamplingInterval(
+    const AHeapProfileEnableCallbackInfo* session_info) {
+  return session_info->sampling_interval;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_create(
+    const char* heap_name) {
+  size_t len = strlen(heap_name);
+  if (len >= sizeof(AHeapInfo::heap_name)) {
+    return nullptr;
   }
+
   uint32_t next_id = g_next_heap_id.fetch_add(1);
   if (next_id >= perfetto::base::ArraySize(g_heaps)) {
-    return 0;
+    return nullptr;
   }
 
   if (next_id == kMinHeapId)
     perfetto::profiling::StartHeapprofdIfStatic();
 
-  HeapprofdHeapInfoInternal& heap = GetHeap(next_id);
-  memcpy(&heap.info, info, n);
-  heap.ready.store(true, std::memory_order_release);
-  return next_id;
+  AHeapInfo& info = GetHeap(next_id);
+  strncpy(info.heap_name, heap_name, sizeof(info.heap_name));
+  return &info;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setEnabledCallback(
+    AHeapInfo* info,
+    void (*callback)(void*, const AHeapProfileEnableCallbackInfo*),
+    void* data) {
+  if (info == nullptr)
+    return nullptr;
+  if (info->ready.load(std::memory_order_relaxed))
+    return nullptr;
+  info->enabled_callback = callback;
+  info->enabled_callback_data = data;
+  return info;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setDisabledCallback(
+    AHeapInfo* info,
+    void (*callback)(void*, const AHeapProfileDisableCallbackInfo*),
+    void* data) {
+  if (info == nullptr)
+    return nullptr;
+  if (info->ready.load(std::memory_order_relaxed))
+    return nullptr;
+  info->disabled_callback = callback;
+  info->disabled_callback_data = data;
+  return info;
+}
+
+__attribute__((visibility("default"))) uint32_t AHeapProfile_registerHeap(
+    AHeapInfo* info) {
+  if (info == nullptr)
+    return 0;
+  info->ready.store(true, std::memory_order_release);
+  return static_cast<uint32_t>(info - &g_heaps[0]);
 }
 
 __attribute__((visibility("default"))) bool
-heapprofd_report_allocation(uint32_t heap_id, uint64_t id, uint64_t size) {
-  const HeapprofdHeapInfoInternal& heap = GetHeap(heap_id);
+AHeapProfile_reportAllocation(uint32_t heap_id, uint64_t id, uint64_t size) {
+  AHeapInfo& heap = GetHeap(heap_id);
   if (!heap.enabled.load(std::memory_order_acquire)) {
     return false;
   }
@@ -250,26 +300,56 @@
     if (!*g_client_ptr)  // no active client (most likely shutting down)
       return false;
 
-    sampled_alloc_sz =
-        (*g_client_ptr)->GetSampleSizeLocked(static_cast<size_t>(size));
+    if (s.blocked_us()) {
+      (*g_client_ptr)->AddClientSpinlockBlockedUs(s.blocked_us());
+    }
+
+    sampled_alloc_sz = heap.sampler.SampleSize(static_cast<size_t>(size));
     if (sampled_alloc_sz == 0)  // not sampling
       return false;
 
     client = *g_client_ptr;  // owning copy
   }                          // unlock
 
-  if (!client->RecordMalloc(
-          heap.service_heap_id.load(std::memory_order_relaxed),
-          sampled_alloc_sz, size, id)) {
+  if (!client->RecordMalloc(heap_id, sampled_alloc_sz, size, id)) {
     ShutdownLazy(client);
   }
   return true;
 }
 
-__attribute__((visibility("default"))) void heapprofd_report_free(
+__attribute__((visibility("default"))) bool
+AHeapProfile_reportSample(uint32_t heap_id, uint64_t id, uint64_t size) {
+  const AHeapInfo& heap = GetHeap(heap_id);
+  if (!heap.enabled.load(std::memory_order_acquire)) {
+    return false;
+  }
+  std::shared_ptr<perfetto::profiling::Client> client;
+  {
+    ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
+    if (PERFETTO_UNLIKELY(!s.locked()))
+      AbortOnSpinlockTimeout();
+
+    auto* g_client_ptr = GetClientLocked();
+    if (!*g_client_ptr)  // no active client (most likely shutting down)
+      return false;
+
+    if (s.blocked_us()) {
+      (*g_client_ptr)->AddClientSpinlockBlockedUs(s.blocked_us());
+    }
+
+    client = *g_client_ptr;  // owning copy
+  }                          // unlock
+
+  if (!client->RecordMalloc(heap_id, size, size, id)) {
+    ShutdownLazy(client);
+  }
+  return true;
+}
+
+__attribute__((visibility("default"))) void AHeapProfile_reportFree(
     uint32_t heap_id,
     uint64_t id) {
-  const HeapprofdHeapInfoInternal& heap = GetHeap(heap_id);
+  const AHeapInfo& heap = GetHeap(heap_id);
   if (!heap.enabled.load(std::memory_order_acquire)) {
     return;
   }
@@ -280,16 +360,19 @@
       AbortOnSpinlockTimeout();
 
     client = *GetClientLocked();  // owning copy (or empty)
+    if (!client)
+      return;
+
+    if (s.blocked_us()) {
+      client->AddClientSpinlockBlockedUs(s.blocked_us());
+    }
   }
 
-  if (client) {
-    if (!client->RecordFree(
-            heap.service_heap_id.load(std::memory_order_relaxed), id))
-      ShutdownLazy(client);
-  }
+  if (!client->RecordFree(heap_id, id))
+    ShutdownLazy(client);
 }
 
-__attribute__((visibility("default"))) bool heapprofd_init_session(
+__attribute__((visibility("default"))) bool AHeapProfile_initSession(
     void* (*malloc_fn)(size_t),
     void (*free_fn)(void*)) {
   static bool first_init = true;
@@ -341,39 +424,48 @@
   const perfetto::profiling::ClientConfiguration& cli_config =
       client->client_config();
 
-  for (uint32_t j = kMinHeapId; j < g_next_heap_id.load(); ++j) {
-    HeapprofdHeapInfoInternal& heap = GetHeap(j);
+  uint64_t heap_intervals[perfetto::base::ArraySize(g_heaps)] = {};
+  uint32_t max_heap = g_next_heap_id.load();
+  for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
+    AHeapInfo& heap = GetHeap(i);
     if (!heap.ready.load(std::memory_order_acquire))
       continue;
 
-    bool matched = false;
-    for (uint32_t i = 0; i < cli_config.num_heaps; ++i) {
-      static_assert(sizeof(g_heaps[0].info.heap_name) == HEAPPROFD_HEAP_NAME_SZ,
-                    "correct heap name size");
-      static_assert(sizeof(cli_config.heaps[0]) == HEAPPROFD_HEAP_NAME_SZ,
-                    "correct heap name size");
-      if (strncmp(&cli_config.heaps[i][0], &heap.info.heap_name[0],
-                  HEAPPROFD_HEAP_NAME_SZ) == 0) {
-        heap.service_heap_id.store(i, std::memory_order_relaxed);
-        if (!heap.enabled.load(std::memory_order_acquire) && heap.info.callback)
-          heap.info.callback(true);
-        heap.enabled.store(true, std::memory_order_release);
-        matched = true;
-        break;
+    heap_intervals[i] = GetHeapSamplingInterval(cli_config, heap.heap_name);
+    // The callbacks must be called while NOT LOCKED. Because they run
+    // arbitrary code, it would be very easy to build a deadlock.
+    if (heap_intervals[i]) {
+      AHeapProfileEnableCallbackInfo session_info{heap_intervals[i]};
+      if (!heap.enabled.load(std::memory_order_acquire) &&
+          heap.enabled_callback) {
+        heap.enabled_callback(heap.enabled_callback_data, &session_info);
+      }
+      heap.enabled.store(true, std::memory_order_release);
+      client->RecordHeapName(i, &heap.heap_name[0]);
+    } else if (heap.enabled.load(std::memory_order_acquire)) {
+      heap.enabled.store(false, std::memory_order_release);
+      if (heap.disabled_callback) {
+        AHeapProfileDisableCallbackInfo info;
+        heap.disabled_callback(heap.disabled_callback_data, &info);
       }
     }
-    if (!matched && heap.enabled.load(std::memory_order_acquire)) {
-      heap.enabled.store(false, std::memory_order_release);
-      if (heap.info.callback)
-        heap.info.callback(false);
-    }
   }
+
   PERFETTO_LOG("%s: heapprofd_client initialized.", getprogname());
   {
     ScopedSpinlock s(&g_client_lock, ScopedSpinlock::Mode::Try);
     if (PERFETTO_UNLIKELY(!s.locked()))
       AbortOnSpinlockTimeout();
 
+    // This needs to happen under the lock for mutual exclusion regarding the
+    // random engine.
+    for (uint32_t i = kMinHeapId; i < max_heap; ++i) {
+      AHeapInfo& heap = GetHeap(i);
+      if (heap_intervals[i]) {
+        heap.sampler.SetSamplingInterval(heap_intervals[i]);
+      }
+    }
+
     // This cannot have been set in the meantime. There are never two concurrent
     // calls to this function, as Bionic uses atomics to guard against that.
     PERFETTO_DCHECK(*GetClientLocked() == nullptr);
diff --git a/src/profiling/memory/client_ext_android.cc b/src/profiling/memory/client_api_android.cc
similarity index 98%
rename from src/profiling/memory/client_ext_android.cc
rename to src/profiling/memory/client_api_android.cc
index 318fb6f..ede75b1 100644
--- a/src/profiling/memory/client_ext_android.cc
+++ b/src/profiling/memory/client_api_android.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/profiling/memory/client_ext_factory.h"
+#include "src/profiling/memory/client_api_factory.h"
 
 #include <string>
 
@@ -74,7 +74,7 @@
   }
   // best effort chdir & fd close
   chdir("/");
-  int fd = open("/dev/null", O_RDWR, 0);
+  int fd = open("/dev/null", O_RDWR, 0);  // NOLINT(android-cloexec-open)
   if (fd != -1) {
     dup2(fd, STDIN_FILENO);
     dup2(fd, STDOUT_FILENO);
diff --git a/src/profiling/memory/client_ext_factory.h b/src/profiling/memory/client_api_factory.h
similarity index 86%
rename from src/profiling/memory/client_ext_factory.h
rename to src/profiling/memory/client_api_factory.h
index fee7767..ea607a6 100644
--- a/src/profiling/memory/client_ext_factory.h
+++ b/src/profiling/memory/client_api_factory.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
-#define SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+#ifndef SRC_PROFILING_MEMORY_CLIENT_API_FACTORY_H_
+#define SRC_PROFILING_MEMORY_CLIENT_API_FACTORY_H_
 
 #include <memory>
 
@@ -33,4 +33,4 @@
 }  // namespace profiling
 }  // namespace perfetto
 
-#endif  // SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+#endif  // SRC_PROFILING_MEMORY_CLIENT_API_FACTORY_H_
diff --git a/src/profiling/memory/client_api_noop.cc b/src/profiling/memory/client_api_noop.cc
new file mode 100644
index 0000000..0666de8
--- /dev/null
+++ b/src/profiling/memory/client_api_noop.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/profiling/memory/heap_profile.h"
+
+#include <inttypes.h>
+
+__attribute__((visibility("default"))) uint64_t
+AHeapProfileEnableCallbackInfo_getSamplingInterval(
+    const AHeapProfileEnableCallbackInfo*) {
+  return 0;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_create(
+    const char*) {
+  return nullptr;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setEnabledCallback(
+    AHeapInfo*,
+    void (*)(void*, const AHeapProfileEnableCallbackInfo*),
+    void*) {
+  return nullptr;
+}
+
+__attribute__((visibility("default"))) AHeapInfo* AHeapInfo_setDisabledCallback(
+    AHeapInfo*,
+    void (*)(void*, const AHeapProfileDisableCallbackInfo*),
+    void*) {
+  return nullptr;
+}
+
+__attribute__((visibility("default"))) uint32_t AHeapProfile_registerHeap(
+    AHeapInfo*) {
+  return 0;
+}
+
+__attribute__((visibility("default"))) bool
+AHeapProfile_reportAllocation(uint32_t, uint64_t, uint64_t) {
+  return false;
+}
+
+__attribute__((visibility("default"))) bool
+AHeapProfile_reportSample(uint32_t, uint64_t, uint64_t) {
+  return false;
+}
+
+__attribute__((visibility("default"))) void AHeapProfile_reportFree(uint32_t,
+                                                                    uint64_t) {}
+
+__attribute__((visibility("default"))) bool AHeapProfile_initSession(
+    void* (*)(size_t),
+    void (*)(void*)) {
+  return false;
+}
diff --git a/src/profiling/memory/client_ext_standalone.cc b/src/profiling/memory/client_api_standalone.cc
similarity index 94%
rename from src/profiling/memory/client_ext_standalone.cc
rename to src/profiling/memory/client_api_standalone.cc
index d3718fe..b9562bf 100644
--- a/src/profiling/memory/client_ext_standalone.cc
+++ b/src/profiling/memory/client_api_standalone.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/profiling/memory/client_ext_factory.h"
+#include "src/profiling/memory/client_api_factory.h"
 
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/unix_socket.h"
@@ -22,7 +22,7 @@
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/watchdog.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/memory/heapprofd_producer.h"
 
@@ -54,7 +54,7 @@
     char buf[1];
     ssize_t r = g_client_sock->Receive(buf, sizeof(buf));
     if (r >= 1) {
-      heapprofd_init_session(malloc, free);
+      AHeapProfile_initSession(malloc, free);
     } else if (r == 0) {
       PERFETTO_ELOG("Server disconneced.");
       break;
@@ -108,7 +108,7 @@
   cli_sock.ReleaseFd();
 
   // Leave stderr open for logging.
-  int null = open("/dev/null", O_RDWR);
+  int null = open("/dev/null", O_RDWR);  // NOLINT(android-cloexec-open)
   dup2(null, STDIN_FILENO);
   dup2(null, STDOUT_FILENO);
   if (null > STDERR_FILENO)
@@ -149,7 +149,7 @@
   task_runner.Run();
 }
 
-// This is called by heapprofd_init_session (client_ext.cc) to construct a
+// This is called by AHeapProfile_initSession (client_api.cc) to construct a
 // client.
 std::shared_ptr<Client> ConstructClient(
     UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
diff --git a/src/profiling/memory/client_unittest.cc b/src/profiling/memory/client_unittest.cc
index 62f0909..b0e3d4c 100644
--- a/src/profiling/memory/client_unittest.cc
+++ b/src/profiling/memory/client_unittest.cc
@@ -16,6 +16,8 @@
 
 #include "src/profiling/memory/client.h"
 
+#include <signal.h>
+
 #include <thread>
 
 #include "perfetto/base/thread_utils.h"
@@ -27,18 +29,66 @@
 namespace profiling {
 namespace {
 
-TEST(ClientTest, GetThreadStackBase) {
+TEST(ClientTest, GetThreadStackRangeBase) {
   std::thread th([] {
-    const char* stackbase = GetThreadStackBase();
-    ASSERT_NE(stackbase, nullptr);
+    StackRange stackrange = GetThreadStackRange();
+    ASSERT_NE(stackrange.begin, nullptr);
+    ASSERT_NE(stackrange.end, nullptr);
     // The implementation assumes the stack grows from higher addresses to
     // lower. We will need to rework once we encounter architectures where the
     // stack grows the other way.
-    EXPECT_GT(stackbase, __builtin_frame_address(0));
+    EXPECT_LT(stackrange.begin, __builtin_frame_address(0));
+    EXPECT_GT(stackrange.end, __builtin_frame_address(0));
   });
   th.join();
 }
 
+TEST(ClientTest, GetSigaltStackRange) {
+  char stack[4096];
+  stack_t altstack{};
+  stack_t old_altstack{};
+  altstack.ss_sp = stack;
+  altstack.ss_size = sizeof(stack);
+  ASSERT_NE(sigaltstack(&altstack, &old_altstack), -1);
+
+  struct sigaction oldact;
+  struct sigaction newact {};
+
+  static StackRange stackrange;
+  static const char* stackptr;
+  newact.sa_handler = [](int) {
+    stackrange = GetSigAltStackRange();
+    stackptr = static_cast<char*>(__builtin_frame_address(0));
+  };
+  newact.sa_flags = SA_ONSTACK;
+  int res = sigaction(SIGUSR1, &newact, &oldact);
+  ASSERT_NE(res, -1);
+
+  raise(SIGUSR1);
+
+  PERFETTO_CHECK(sigaction(SIGUSR1, &oldact, nullptr) != -1);
+  PERFETTO_CHECK(sigaltstack(&old_altstack, nullptr) != -1);
+
+  ASSERT_EQ(stackrange.begin, stack);
+  ASSERT_EQ(stackrange.end, &stack[4096]);
+  ASSERT_LT(stackrange.begin, stackptr);
+  ASSERT_GT(stackrange.end, stackptr);
+}
+
+TEST(ClientTest, GetMainThreadStackRange) {
+  if (getpid() != base::GetThreadId())
+    GTEST_SKIP() << "This test has to run on the main thread.";
+
+  StackRange stackrange = GetMainThreadStackRange();
+  ASSERT_NE(stackrange.begin, nullptr);
+  ASSERT_NE(stackrange.end, nullptr);
+  // The implementation assumes the stack grows from higher addresses to
+  // lower. We will need to rework once we encounter architectures where the
+  // stack grows the other way.
+  EXPECT_LT(stackrange.begin, __builtin_frame_address(0));
+  EXPECT_GT(stackrange.end, __builtin_frame_address(0));
+}
+
 TEST(ClientTest, IsMainThread) {
   // Our code relies on the fact that getpid() == GetThreadId() if this
   // process/thread is the main thread of the process. This test ensures that is
diff --git a/src/profiling/memory/heapprofd_client_api.map.txt b/src/profiling/memory/heapprofd_client_api.map.txt
index 35cd18d..5c578d4 100644
--- a/src/profiling/memory/heapprofd_client_api.map.txt
+++ b/src/profiling/memory/heapprofd_client_api.map.txt
@@ -1,9 +1,14 @@
 {
   global:
-    heapprofd_init_session;
-    heapprofd_report_free;
-    heapprofd_register_heap;
-    heapprofd_report_allocation;
+    AHeapProfileEnableCallbackInfo_getSamplingInterval;
+    AHeapProfile_reportSample;
+    AHeapProfile_reportFree;
+    AHeapProfile_reportAllocation;
+    AHeapProfile_registerHeap;
+    AHeapProfile_initSession;
+    AHeapInfo_setEnabledCallback;
+    AHeapInfo_setDisabledCallback;
+    AHeapInfo_create;
   local:
     *;
 };
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index 4499eda..f6f51f7 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// End to end tests for heapprofd.
-// None of these tests currently pass on non-Android, but we still build most
-// of it as a best-effort way to maintain the out-of-tree build.
-
 #include <atomic>
 #include <string>
 
@@ -28,11 +24,15 @@
 #include <unistd.h>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/subprocess.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
 #include "src/base/test/test_task_runner.h"
 #include "src/profiling/memory/heapprofd_producer.h"
 #include "test/gtest_and_gmock.h"
@@ -43,6 +43,7 @@
 #endif
 
 #include "protos/perfetto/config/profiling/heapprofd_config.gen.h"
+#include "protos/perfetto/trace/interned_data/interned_data.gen.h"
 #include "protos/perfetto/trace/profiling/profile_common.gen.h"
 #include "protos/perfetto/trace/profiling/profile_packet.gen.h"
 
@@ -71,14 +72,14 @@
 std::string AllocatorName(AllocatorMode mode) {
   switch (mode) {
     case AllocatorMode::kMalloc:
-      return "malloc";
+      return "libc.malloc";
     case AllocatorMode::kCustom:
       return "test";
   }
 }
 
 AllocatorMode AllocatorModeFromNameOrDie(std::string s) {
-  if (s == "malloc")
+  if (s == "libc.malloc")
     return AllocatorMode::kMalloc;
   if (s == "test")
     return AllocatorMode::kCustom;
@@ -165,17 +166,16 @@
 #endif
 
 void CustomAllocateAndFree(size_t bytes) {
-  HeapprofdHeapInfo info{"test", nullptr};
-  static uint32_t heap_id = heapprofd_register_heap(&info, sizeof(info));
-  heapprofd_report_allocation(heap_id, 0x1234abc, bytes);
-  heapprofd_report_free(heap_id, 0x1234abc);
+  static uint32_t heap_id = AHeapProfile_registerHeap(AHeapInfo_create("test"));
+  AHeapProfile_reportAllocation(heap_id, 0x1234abc, bytes);
+  AHeapProfile_reportFree(heap_id, 0x1234abc);
 }
 
 void SecondaryAllocAndFree(size_t bytes) {
-  HeapprofdHeapInfo info{"secondary", nullptr};
-  static uint32_t heap_id = heapprofd_register_heap(&info, sizeof(info));
-  heapprofd_report_allocation(heap_id, 0x1234abc, bytes);
-  heapprofd_report_free(heap_id, 0x1234abc);
+  static uint32_t heap_id =
+      AHeapProfile_registerHeap(AHeapInfo_create("secondary"));
+  AHeapProfile_reportAllocation(heap_id, 0x1234abc, bytes);
+  AHeapProfile_reportFree(heap_id, 0x1234abc);
 }
 
 void AllocateAndFree(size_t bytes) {
@@ -288,9 +288,13 @@
     return;
 
   static std::atomic<bool> initialized{false};
-  HeapprofdHeapInfo info{"test", [](bool) { initialized = true; }};
-
-  static uint32_t heap_id = heapprofd_register_heap(&info, sizeof(info));
+  static uint32_t heap_id =
+      AHeapProfile_registerHeap(AHeapInfo_setEnabledCallback(
+          AHeapInfo_create("test"),
+          [](void*, const AHeapProfileEnableCallbackInfo*) {
+            initialized = true;
+          },
+          nullptr));
 
   ChildFinishHandshake();
 
@@ -300,11 +304,14 @@
   // We call the callback before setting enabled=true on the heap, so we
   // wait a bit for the assignment to happen.
   usleep(100000);
-  heapprofd_report_allocation(heap_id, 0x1, 10u);
-  heapprofd_report_free(heap_id, 0x1);
-  heapprofd_report_allocation(heap_id, 0x2, 15u);
-  heapprofd_report_allocation(heap_id, 0x3, 15u);
-  heapprofd_report_free(heap_id, 0x2);
+  if (!AHeapProfile_reportAllocation(heap_id, 0x1, 10u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  AHeapProfile_reportFree(heap_id, 0x1);
+  if (!AHeapProfile_reportAllocation(heap_id, 0x2, 15u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  if (!AHeapProfile_reportAllocation(heap_id, 0x3, 15u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  AHeapProfile_reportFree(heap_id, 0x2);
 
   // Wait around so we can verify it did't crash.
   for (;;) {
@@ -355,6 +362,20 @@
   return helper;
 }
 
+std::string ToTraceString(
+    const std::vector<protos::gen::TracePacket>& packets) {
+  protos::gen::Trace trace;
+  for (const protos::gen::TracePacket& packet : packets) {
+    *trace.add_packet() = packet;
+  }
+  return trace.SerializeAsString();
+}
+
+#define WRITE_TRACE(trace)                 \
+  do {                                     \
+    WriteTrace(trace, __FILE__, __LINE__); \
+  } while (0)
+
 std::string FormatHistogram(const protos::gen::ProfilePacket_Histogram& hist) {
   std::string out;
   std::string prev_upper_limit = "-inf";
@@ -380,10 +401,9 @@
          "unwinding_time_us: " + FormatHistogram(stats.unwinding_time_us());
 }
 
-__attribute__((unused)) std::string TestSuffix(
-    const ::testing::TestParamInfo<std::tuple<TestMode, AllocatorMode>>& info) {
-  TestMode tm = std::get<0>(info.param);
-  AllocatorMode am = std::get<1>(info.param);
+std::string Suffix(const std::tuple<TestMode, AllocatorMode>& param) {
+  TestMode tm = std::get<0>(param);
+  AllocatorMode am = std::get<1>(param);
 
   std::string result;
   switch (tm) {
@@ -408,6 +428,11 @@
   return result;
 }
 
+__attribute__((unused)) std::string TestSuffix(
+    const ::testing::TestParamInfo<std::tuple<TestMode, AllocatorMode>>& info) {
+  return Suffix(info.param);
+}
+
 class HeapprofdEndToEnd
     : public ::testing::TestWithParam<std::tuple<TestMode, AllocatorMode>> {
  public:
@@ -437,11 +462,25 @@
       nullptr};
 
   TestMode test_mode() { return std::get<0>(GetParam()); }
-
   AllocatorMode allocator_mode() { return std::get<1>(GetParam()); }
-
   std::string allocator_name() { return AllocatorName(allocator_mode()); }
 
+  void WriteTrace(const std::vector<protos::gen::TracePacket>& packets,
+                  const char* filename,
+                  uint64_t lineno) {
+    const char* outdir = getenv("HEAPPROFD_TEST_PROFILE_OUT");
+    if (!outdir)
+      return;
+    const std::string fq_filename =
+        std::string(outdir) + "/" + basename(filename) + ":" +
+        std::to_string(lineno) + "_" + Suffix(GetParam());
+    base::ScopedFile fd(base::OpenFile(fq_filename, O_WRONLY | O_CREAT, 0666));
+    PERFETTO_CHECK(*fd);
+    std::string trace_string = ToTraceString(packets);
+    PERFETTO_CHECK(
+        base::WriteAll(*fd, trace_string.data(), trace_string.size()) >= 0);
+  }
+
   std::unique_ptr<TestHelper> Trace(const TraceConfig& trace_config) {
     auto helper = GetHelper(&task_runner);
 
@@ -453,6 +492,20 @@
     return helper;
   }
 
+  std::vector<std::string> GetUnwindingErrors(TestHelper* helper) {
+    std::vector<std::string> out;
+    const auto& packets = helper->trace();
+    for (const protos::gen::TracePacket& packet : packets) {
+      for (const protos::gen::InternedString& fn :
+           packet.interned_data().function_names()) {
+        if (fn.str().find("ERROR ") == 0) {
+          out.push_back(fn.str());
+        }
+      }
+    }
+    return out;
+  }
+
   void PrintStats(TestHelper* helper) {
     const auto& packets = helper->trace();
     for (const protos::gen::TracePacket& packet : packets) {
@@ -462,6 +515,10 @@
                      FormatStats(dump.stats()).c_str());
       }
     }
+    std::vector<std::string> errors = GetUnwindingErrors(helper);
+    for (const std::string& err : errors) {
+      PERFETTO_LOG("Unwinding error: %s", err.c_str());
+    }
   }
 
   void ValidateSampleSizes(TestHelper* helper,
@@ -567,7 +624,8 @@
 // This checks that the child is still running (to ensure it didn't crash
 // unxpectedly) and then kills it.
 void KillAssertRunning(base::Subprocess* child) {
-  ASSERT_EQ(child->Poll(), base::Subprocess::kRunning);
+  ASSERT_EQ(child->Poll(), base::Subprocess::kRunning)
+      << "Target process not running. CHECK CRASH LOGS.";
   child->KillAndWaitForTermination();
 }
 
@@ -585,10 +643,11 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+  KillAssertRunning(&child);
 
   ValidateNoSamples(helper.get(), pid);
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, Smoke) {
@@ -605,12 +664,13 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+  KillAssertRunning(&child);
+
   ValidateHasSamples(helper.get(), pid, allocator_name());
   ValidateOnlyPID(helper.get(), pid);
   ValidateSampleSizes(helper.get(), pid, kAllocSize);
-
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, TwoAllocators) {
@@ -630,14 +690,42 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+  KillAssertRunning(&child);
+
   ValidateHasSamples(helper.get(), pid, "secondary");
   ValidateHasSamples(helper.get(), pid, allocator_name());
   ValidateOnlyPID(helper.get(), pid);
   ValidateSampleSizes(helper.get(), pid, kCustomAllocSize, "secondary");
   ValidateSampleSizes(helper.get(), pid, kAllocSize, allocator_name());
+}
 
+TEST_P(HeapprofdEndToEnd, TwoAllocatorsAll) {
+  constexpr size_t kCustomAllocSize = 1024;
+  constexpr size_t kAllocSize = 7;
+
+  base::Subprocess child =
+      ForkContinuousAlloc(allocator_mode(), kAllocSize, kCustomAllocSize);
+  const uint64_t pid = static_cast<uint64_t>(child.pid());
+
+  TraceConfig trace_config = MakeTraceConfig([pid](HeapprofdConfig* cfg) {
+    cfg->set_sampling_interval_bytes(1);
+    cfg->add_pid(pid);
+    cfg->set_all_heaps(true);
+    ContinuousDump(cfg);
+  });
+
+  auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
+  PrintStats(helper.get());
   KillAssertRunning(&child);
+
+  ValidateHasSamples(helper.get(), pid, "secondary");
+  ValidateHasSamples(helper.get(), pid, allocator_name());
+  ValidateOnlyPID(helper.get(), pid);
+  ValidateSampleSizes(helper.get(), pid, kCustomAllocSize, "secondary");
+  ValidateSampleSizes(helper.get(), pid, kAllocSize, allocator_name());
 }
 
 TEST_P(HeapprofdEndToEnd, AccurateCustom) {
@@ -660,7 +748,10 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+  KillAssertRunning(&child);
+
   ValidateOnlyPID(helper.get(), pid);
 
   size_t total_alloc = 0;
@@ -675,7 +766,47 @@
   }
   EXPECT_EQ(total_alloc, 40u);
   EXPECT_EQ(total_freed, 25u);
+}
+
+TEST_P(HeapprofdEndToEnd, AccurateDumpAtMaxCustom) {
+  if (allocator_mode() != AllocatorMode::kCustom)
+    GTEST_SKIP();
+
+  base::Subprocess child({"/proc/self/exe"});
+  child.args.argv0_override = "heapprofd_continuous_malloc";
+  child.args.stdout_mode = base::Subprocess::kDevNull;
+  child.args.stderr_mode = base::Subprocess::kDevNull;
+  child.args.env.push_back("HEAPPROFD_TESTING_RUN_ACCURATE_MALLOC=1");
+  StartAndWaitForHandshake(&child);
+
+  const uint64_t pid = static_cast<uint64_t>(child.pid());
+
+  TraceConfig trace_config = MakeTraceConfig([pid](HeapprofdConfig* cfg) {
+    cfg->set_sampling_interval_bytes(1);
+    cfg->add_pid(pid);
+    cfg->add_heaps("test");
+    cfg->set_dump_at_max(true);
+  });
+
+  auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
+  PrintStats(helper.get());
   KillAssertRunning(&child);
+
+  ValidateOnlyPID(helper.get(), pid);
+
+  size_t total_alloc = 0;
+  size_t total_count = 0;
+  for (const protos::gen::TracePacket& packet : helper->trace()) {
+    for (const auto& dump : packet.profile_packet().process_dumps()) {
+      for (const auto& sample : dump.samples()) {
+        total_alloc += sample.self_max();
+        total_count += sample.self_max_count();
+      }
+    }
+  }
+  EXPECT_EQ(total_alloc, 30u);
+  EXPECT_EQ(total_count, 2u);
 }
 
 TEST_P(HeapprofdEndToEnd, TwoProcesses) {
@@ -696,15 +827,17 @@
       });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+
+  KillAssertRunning(&child);
+  KillAssertRunning(&child2);
+
   ValidateHasSamples(helper.get(), pid, allocator_name());
   ValidateSampleSizes(helper.get(), pid, kAllocSize);
   ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid2),
                      allocator_name());
   ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid2), kAllocSize2);
-
-  KillAssertRunning(&child);
-  KillAssertRunning(&child2);
 }
 
 TEST_P(HeapprofdEndToEnd, FinalFlush) {
@@ -719,12 +852,13 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
+  KillAssertRunning(&child);
+
   ValidateHasSamples(helper.get(), pid, allocator_name());
   ValidateOnlyPID(helper.get(), pid);
   ValidateSampleSizes(helper.get(), pid, kAllocSize);
-
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, NativeStartup) {
@@ -768,6 +902,7 @@
 
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
 
   KillAssertRunning(&child);
 
@@ -840,6 +975,7 @@
 
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
 
   KillAssertRunning(&child);
 
@@ -902,6 +1038,7 @@
 
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
 
   KillAssertRunning(&child);
 
@@ -965,6 +1102,7 @@
 
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
 
   KillAssertRunning(&child);
 
@@ -1040,6 +1178,7 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
 
   PrintStats(helper.get());
   ValidateHasSamples(helper.get(), pid, allocator_name());
@@ -1069,13 +1208,14 @@
 
   helper2->ReadData();
   helper2->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper2->trace());
 
   PrintStats(helper2.get());
+  KillAssertRunning(&child);
+
   ValidateHasSamples(helper2.get(), pid, allocator_name());
   ValidateOnlyPID(helper2.get(), pid);
   ValidateSampleSizes(helper2.get(), pid, kSecondIterationBytes);
-
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, ReInitAfterInvalid) {
@@ -1123,6 +1263,7 @@
   });
 
   auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
 
   PrintStats(helper.get());
   ValidateHasSamples(helper.get(), pid, allocator_name());
@@ -1152,19 +1293,17 @@
 
   helper2->ReadData();
   helper2->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper2->trace());
 
   PrintStats(helper2.get());
+  KillAssertRunning(&child);
+
   ValidateHasSamples(helper2.get(), pid, allocator_name());
   ValidateOnlyPID(helper2.get(), pid);
   ValidateSampleSizes(helper2.get(), pid, kSecondIterationBytes);
-
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
-  // TODO(fmayer): We do not correctly mark rejected sessions in static mode.
-  if (test_mode() == TestMode::kStatic)
-    GTEST_SKIP();
   constexpr size_t kAllocSize = 1024;
 
   base::Subprocess child = ForkContinuousAlloc(allocator_mode(), kAllocSize);
@@ -1191,20 +1330,23 @@
   helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
   PrintStats(helper.get());
-  ValidateHasSamples(helper.get(), pid, allocator_name());
-  ValidateOnlyPID(helper.get(), pid);
-  ValidateSampleSizes(helper.get(), pid, kAllocSize);
-  ValidateRejectedConcurrent(helper_concurrent.get(), pid, false);
 
   helper_concurrent->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
   helper_concurrent->ReadData();
   helper_concurrent->WaitForReadData(0, kWaitForReadDataTimeoutMs);
-  PrintStats(helper.get());
+  WRITE_TRACE(helper_concurrent->trace());
+  PrintStats(helper_concurrent.get());
+  KillAssertRunning(&child);
+
+  ValidateHasSamples(helper.get(), pid, allocator_name());
+  ValidateOnlyPID(helper.get(), pid);
+  ValidateSampleSizes(helper.get(), pid, kAllocSize);
+  ValidateRejectedConcurrent(helper.get(), pid, false);
+
   ValidateOnlyPID(helper_concurrent.get(), pid);
   ValidateRejectedConcurrent(helper_concurrent.get(), pid, true);
-
-  KillAssertRunning(&child);
 }
 
 TEST_P(HeapprofdEndToEnd, NativeProfilingActiveAtProcessExit) {
@@ -1264,6 +1406,7 @@
   helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
   helper->ReadData();
   helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
+  WRITE_TRACE(helper->full_trace());
 
   const auto& packets = helper->trace();
   ASSERT_GT(packets.size(), 0u);
@@ -1297,13 +1440,11 @@
 #error "Need to start daemons for Linux test."
 #endif
 
-#if !defined(THREAD_SANITIZER)
 INSTANTIATE_TEST_CASE_P(Run,
                         HeapprofdEndToEnd,
                         Values(std::make_tuple(TestMode::kStatic,
                                                AllocatorMode::kCustom)),
                         TestSuffix);
-#endif
 #elif !PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
 INSTANTIATE_TEST_CASE_P(
     Run,
diff --git a/src/profiling/memory/heapprofd_preload.map.txt b/src/profiling/memory/heapprofd_preload.map.txt
new file mode 100644
index 0000000..ca2ffbb
--- /dev/null
+++ b/src/profiling/memory/heapprofd_preload.map.txt
@@ -0,0 +1,15 @@
+{
+  global:
+    malloc;
+    free;
+    calloc;
+    realloc;
+    posix_memalign;
+    aligned_alloc;
+    memalign;
+    pvalloc;
+    valloc;
+    reallocarray;
+  local:
+    *;
+};
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index c604a79..4b1d02f 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <string>
 
 #include <inttypes.h>
 #include <signal.h>
@@ -25,15 +26,27 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/watchdog_posix.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/ext/tracing/ipc/producer_ipc_client.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
+#include "src/profiling/common/profiler_guardrails.h"
+#include "src/profiling/memory/unwound_messages.h"
+#include "src/profiling/memory/wire_protocol.h"
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/system_properties.h>
+#endif
 
 namespace perfetto {
 namespace profiling {
@@ -94,40 +107,75 @@
   return i;
 }
 
+bool IsFile(int fd, const char* fn) {
+  struct stat fdstat;
+  struct stat fnstat;
+  if (fstat(fd, &fdstat) == -1) {
+    PERFETTO_PLOG("fstat");
+    return false;
+  }
+  if (lstat(fn, &fnstat) == -1) {
+    PERFETTO_PLOG("lstat");
+    return false;
+  }
+  return fdstat.st_ino == fnstat.st_ino;
+}
+
 }  // namespace
 
-void HeapprofdConfigToClientConfiguration(
+bool HeapprofdConfigToClientConfiguration(
     const HeapprofdConfig& heapprofd_config,
     ClientConfiguration* cli_config) {
-  cli_config->interval = heapprofd_config.sampling_interval_bytes();
+  cli_config->default_interval = heapprofd_config.sampling_interval_bytes();
   cli_config->block_client = heapprofd_config.block_client();
   cli_config->disable_fork_teardown = heapprofd_config.disable_fork_teardown();
   cli_config->disable_vfork_detection =
       heapprofd_config.disable_vfork_detection();
   cli_config->block_client_timeout_us =
       heapprofd_config.block_client_timeout_us();
+  cli_config->all_heaps = heapprofd_config.all_heaps();
   size_t n = 0;
   std::vector<std::string> heaps = heapprofd_config.heaps();
+  std::vector<uint64_t> heap_intervals =
+      heapprofd_config.heap_sampling_intervals();
   if (heaps.empty()) {
-    heaps.push_back("malloc");
+    heaps.push_back("libc.malloc");
+  }
+
+  if (heap_intervals.empty()) {
+    heap_intervals.assign(heaps.size(),
+                          heapprofd_config.sampling_interval_bytes());
+  }
+  if (heap_intervals.size() != heaps.size()) {
+    PERFETTO_ELOG("heap_sampling_intervals and heaps length mismatch.");
+    return false;
+  }
+  if (std::find(heap_intervals.begin(), heap_intervals.end(), 0u) !=
+      heap_intervals.end()) {
+    PERFETTO_ELOG("zero sampling interval.");
+    return false;
   }
   if (heaps.size() > base::ArraySize(cli_config->heaps)) {
     heaps.resize(base::ArraySize(cli_config->heaps));
     PERFETTO_ELOG("Too many heaps requested. Truncating.");
   }
-  for (const std::string& heap : heaps) {
+  for (size_t i = 0; i < heaps.size(); ++i) {
+    const std::string& heap = heaps[i];
+    const uint64_t interval = heap_intervals[i];
     // -1 for the \0 byte.
     if (heap.size() > HEAPPROFD_HEAP_NAME_SZ - 1) {
       PERFETTO_ELOG("Invalid heap name %s (larger than %d)", heap.c_str(),
                     HEAPPROFD_HEAP_NAME_SZ - 1);
       continue;
     }
-    strncpy(&cli_config->heaps[n][0], heap.c_str(),
-            sizeof(cli_config->heaps[0]));
-    cli_config->heaps[n][sizeof(cli_config->heaps[0]) - 1] = '\0';
+    strncpy(&cli_config->heaps[n].name[0], heap.c_str(),
+            sizeof(cli_config->heaps[0].name));
+    cli_config->heaps[n].name[sizeof(cli_config->heaps[0].name) - 1] = '\0';
+    cli_config->heaps[n].interval = interval;
     n++;
   }
   cli_config->num_heaps = n;
+  return true;
 }
 
 const uint64_t LogHistogram::kMaxBucket = 0;
@@ -165,15 +213,8 @@
       unwinding_workers_(MakeUnwindingWorkers(this, kUnwinderThreads)),
       socket_delegate_(this),
       weak_factory_(this) {
-  CheckDataSourceMemory();  // Kick off guardrail task.
-  stat_fd_.reset(open("/proc/self/stat", O_RDONLY | O_CLOEXEC));
-  if (!stat_fd_) {
-    PERFETTO_ELOG(
-        "Failed to open /proc/self/stat. Cannot accept profiles "
-        "with CPU guardrails.");
-  } else {
-    CheckDataSourceCpu();  // Kick off guardrail task.
-  }
+  CheckDataSourceCpuTask();
+  CheckDataSourceMemoryTask();
 }
 
 HeapprofdProducer::~HeapprofdProducer() = default;
@@ -346,6 +387,20 @@
 
 void HeapprofdProducer::OnTracingSetup() {}
 
+void HeapprofdProducer::WriteRejectedConcurrentSession(BufferID buffer_id,
+                                                       pid_t pid) {
+  auto trace_writer = endpoint_->CreateTraceWriter(buffer_id);
+  auto trace_packet = trace_writer->NewTracePacket();
+  trace_packet->set_timestamp(
+      static_cast<uint64_t>(base::GetBootTimeNs().count()));
+  auto profile_packet = trace_packet->set_profile_packet();
+  auto process_dump = profile_packet->add_process_dumps();
+  process_dump->set_pid(static_cast<uint64_t>(pid));
+  process_dump->set_rejected_concurrent(true);
+  trace_packet->Finalize();
+  trace_writer->Flush();
+}
+
 void HeapprofdProducer::SetupDataSource(DataSourceInstanceID id,
                                         const DataSourceConfig& ds_config) {
   PERFETTO_DLOG("Setting up data source.");
@@ -394,23 +449,14 @@
 
       // Manually write one ProfilePacket about the rejected session.
       auto buffer_id = static_cast<BufferID>(ds_config.target_buffer());
-      auto trace_writer = endpoint_->CreateTraceWriter(buffer_id);
-      auto trace_packet = trace_writer->NewTracePacket();
-      trace_packet->set_timestamp(
-          static_cast<uint64_t>(base::GetBootTimeNs().count()));
-      auto profile_packet = trace_packet->set_profile_packet();
-      auto process_dump = profile_packet->add_process_dumps();
-      process_dump->set_pid(static_cast<uint64_t>(target_process_.pid));
-      process_dump->set_rejected_concurrent(true);
-      trace_packet->Finalize();
-      trace_writer->Flush();
+      WriteRejectedConcurrentSession(buffer_id, target_process_.pid);
       return;
     }
   }
 
   base::Optional<uint64_t> start_cputime_sec;
   if (heapprofd_config.max_heapprofd_cpu_secs() > 0) {
-    start_cputime_sec = GetCputimeSec();
+    start_cputime_sec = GetCputimeSecForCurrentProcess();
 
     if (!start_cputime_sec) {
       PERFETTO_ELOG("Failed to enforce CPU guardrail. Rejecting config.");
@@ -422,11 +468,19 @@
   DataSource data_source(endpoint_->CreateTraceWriter(buffer_id));
   data_source.id = id;
   auto& cli_config = data_source.client_configuration;
-  HeapprofdConfigToClientConfiguration(heapprofd_config, &cli_config);
+  if (!HeapprofdConfigToClientConfiguration(heapprofd_config, &cli_config))
+    return;
   data_source.config = heapprofd_config;
+  data_source.ds_config = ds_config;
   data_source.normalized_cmdlines = std::move(normalized_cmdlines.value());
-  data_source.stop_timeout_ms = ds_config.stop_timeout_ms();
-  data_source.start_cputime_sec = start_cputime_sec;
+  data_source.stop_timeout_ms = ds_config.stop_timeout_ms()
+                                    ? ds_config.stop_timeout_ms()
+                                    : 5000 /* kDataSourceStopTimeoutMs */;
+  data_source.guardrail_config.cpu_start_secs = start_cputime_sec;
+  data_source.guardrail_config.memory_guardrail_kb =
+      heapprofd_config.max_heapprofd_memory_kb();
+  data_source.guardrail_config.cpu_guardrail_sec =
+      heapprofd_config.max_heapprofd_cpu_secs();
 
   InterningOutputTracker::WriteFixedInterningsPacket(
       data_source.trace_writer.get());
@@ -440,26 +494,12 @@
 }
 
 bool HeapprofdProducer::IsPidProfiled(pid_t pid) {
-  for (const auto& pair : data_sources_) {
-    const DataSource& ds = pair.second;
-    if (ds.process_states.find(pid) != ds.process_states.cend())
-      return true;
-  }
-  return false;
-}
-
-base::Optional<uint64_t> HeapprofdProducer::GetCputimeSec() {
-  if (!stat_fd_) {
-    return base::nullopt;
-  }
-  lseek(stat_fd_.get(), 0, SEEK_SET);
-  base::ProcStat stat;
-  if (!ReadProcStat(stat_fd_.get(), &stat)) {
-    PERFETTO_ELOG("Failed to read stat file to enforce guardrails.");
-    return base::nullopt;
-  }
-  return (stat.utime + stat.stime) /
-         static_cast<unsigned long>(sysconf(_SC_CLK_TCK));
+  return std::any_of(
+      data_sources_.cbegin(), data_sources_.cend(),
+      [pid](const std::pair<const DataSourceInstanceID, DataSource>& p) {
+        const DataSource& ds = p.second;
+        return ds.process_states.count(pid) > 0;
+      });
 }
 
 void HeapprofdProducer::SetStartupProperties(DataSource* data_source) {
@@ -626,8 +666,11 @@
 
 void HeapprofdProducer::DoContinuousDump(DataSourceInstanceID id,
                                          uint32_t dump_interval) {
-  if (!DumpProcessesInDataSource(id))
+  auto it = data_sources_.find(id);
+  if (it == data_sources_.end())
     return;
+  DataSource& data_source = it->second;
+  DumpProcessesInDataSource(&data_source);
   auto weak_producer = weak_factory_.GetWeakPtr();
   task_runner_->PostDelayedTask(
       [weak_producer, id, dump_interval] {
@@ -652,13 +695,14 @@
       dump_timestamp = heap_tracker.max_timestamp();
     else
       dump_timestamp = heap_tracker.committed_timestamp();
+
     const char* heap_name = nullptr;
-    const ClientConfiguration& cli_config = data_source->client_configuration;
-    if (heap_id < cli_config.num_heaps) {
-      heap_name = cli_config.heaps[heap_id];
-    } else {
+    auto it = process_state->heap_names.find(heap_id);
+    if (it != process_state->heap_names.end())
+      heap_name = it->second.c_str();
+    else
       PERFETTO_ELOG("Invalid heap id %" PRIu32, heap_id);
-    }
+
     auto new_heapsamples =
         [pid, from_startup, dump_timestamp, process_state, data_source,
          heap_name](ProfilePacket::ProcessHeapSamples* proto) {
@@ -677,6 +721,8 @@
           stats->set_map_reparses(process_state->map_reparses);
           stats->set_total_unwinding_time_us(
               process_state->total_unwinding_time_us);
+          stats->set_client_spinlock_blocked_us(
+              process_state->client_spinlock_blocked_us);
           auto* unwinding_hist = stats->set_unwinding_time_us();
           for (const auto& p : process_state->unwinding_time_us.GetData()) {
             auto* bucket = unwinding_hist->add_buckets();
@@ -692,58 +738,27 @@
                          std::move(new_heapsamples),
                          &data_source->intern_state);
 
-    if (process_state->page_idle_checker) {
-      PageIdleChecker& page_idle_checker = *process_state->page_idle_checker;
-      heap_tracker.GetAllocations([&dump_state, &page_idle_checker](
-                                      uint64_t addr, uint64_t,
-                                      uint64_t alloc_size,
-                                      uint64_t callstack_id) {
-        int64_t idle =
-            page_idle_checker.OnIdlePage(addr, static_cast<size_t>(alloc_size));
-        if (idle < 0) {
-          PERFETTO_PLOG("OnIdlePage.");
-          return;
-        }
-        if (idle > 0)
-          dump_state.AddIdleBytes(callstack_id, static_cast<uint64_t>(idle));
-      });
-    }
-
     heap_tracker.GetCallstackAllocations(
         [&dump_state,
          &data_source](const HeapTracker::CallstackAllocations& alloc) {
           dump_state.WriteAllocation(alloc, data_source->config.dump_at_max());
         });
-    if (process_state->page_idle_checker)
-      process_state->page_idle_checker->MarkPagesIdle();
     dump_state.DumpCallstacks(&callsites_);
   }
 }
 
-bool HeapprofdProducer::DumpProcessesInDataSource(DataSourceInstanceID id) {
-  auto it = data_sources_.find(id);
-  if (it == data_sources_.end()) {
-    PERFETTO_LOG(
-        "Data source not found (harmless if using continuous_dump_config).");
-    return false;
-  }
-  DataSource& data_source = it->second;
-
+void HeapprofdProducer::DumpProcessesInDataSource(DataSource* ds) {
   for (std::pair<const pid_t, ProcessState>& pid_and_process_state :
-       data_source.process_states) {
+       ds->process_states) {
     pid_t pid = pid_and_process_state.first;
     ProcessState& process_state = pid_and_process_state.second;
-    DumpProcessState(&data_source, pid, &process_state);
+    DumpProcessState(ds, pid, &process_state);
   }
-
-  return true;
 }
 
 void HeapprofdProducer::DumpAll() {
-  for (const auto& id_and_data_source : data_sources_) {
-    if (!DumpProcessesInDataSource(id_and_data_source.first))
-      PERFETTO_DLOG("Failed to dump %" PRIu64, id_and_data_source.first);
-  }
+  for (auto& id_and_data_source : data_sources_)
+    DumpProcessesInDataSource(&id_and_data_source.second);
 }
 
 void HeapprofdProducer::Flush(FlushRequestID flush_id,
@@ -830,9 +845,7 @@
   char buf[1];
   self->Receive(buf, sizeof(buf), fds, base::ArraySize(fds));
 
-  static_assert(kHandshakeSize == 3, "change if and else if below.");
-  // We deliberately do not check for fds[kHandshakePageIdle] so we can
-  // degrade gracefully on kernels that do not have the file yet.
+  static_assert(kHandshakeSize == 2, "change if and else if below.");
   if (fds[kHandshakeMaps] && fds[kHandshakeMem]) {
     auto ds_it =
         producer_->data_sources_.find(pending_process.data_source_instance_id);
@@ -848,24 +861,26 @@
       return;
     }
 
-    auto it_and_inserted = data_source.process_states.emplace(
+    std::string maps_file =
+        "/proc/" + std::to_string(self->peer_pid()) + "/maps";
+    if (!IsFile(*fds[kHandshakeMaps], maps_file.c_str())) {
+      producer_->pending_processes_.erase(it);
+      PERFETTO_ELOG("Received invalid maps FD.");
+      return;
+    }
+
+    std::string mem_file = "/proc/" + std::to_string(self->peer_pid()) + "/mem";
+    if (!IsFile(*fds[kHandshakeMem], mem_file.c_str())) {
+      producer_->pending_processes_.erase(it);
+      PERFETTO_ELOG("Received invalid mem FD.");
+      return;
+    }
+
+    data_source.process_states.emplace(
         std::piecewise_construct, std::forward_as_tuple(self->peer_pid()),
         std::forward_as_tuple(&producer_->callsites_,
                               data_source.config.dump_at_max()));
 
-    ProcessState& process_state = it_and_inserted.first->second;
-    if (data_source.config.idle_allocations()) {
-      if (fds[kHandshakePageIdle]) {
-        process_state.page_idle_checker =
-            PageIdleChecker(std::move(fds[kHandshakePageIdle]));
-      } else {
-        PERFETTO_ELOG(
-            "Idle page tracking requested but did not receive "
-            "page_idle file. Continuing without idle page tracking. Please "
-            "check your kernel version.");
-      }
-    }
-
     PERFETTO_DLOG("%d: Received FDs.", self->peer_pid());
     int raw_fd = pending_process.shmem.fd();
     // TODO(fmayer): Full buffer could deadlock us here.
@@ -885,12 +900,12 @@
     handoff_data.mem_fd = std::move(fds[kHandshakeMem]);
     handoff_data.shmem = std::move(pending_process.shmem);
     handoff_data.client_config = data_source.client_configuration;
+    handoff_data.stream_allocations = data_source.config.stream_allocations();
 
     producer_->UnwinderForPID(self->peer_pid())
         .PostHandoffSocket(std::move(handoff_data));
     producer_->pending_processes_.erase(it);
-  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem] ||
-             fds[kHandshakePageIdle]) {
+  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem]) {
     PERFETTO_DFATAL_OR_ELOG("%d: Received partial FDs.", self->peer_pid());
     producer_->pending_processes_.erase(it);
   } else {
@@ -928,6 +943,15 @@
   }
   RecordOtherSourcesAsRejected(data_source, process);
 
+  // In fork mode, right now we check whether the target is not profileable
+  // in the client, because we cannot read packages.list there.
+  if (mode_ == HeapprofdMode::kCentral &&
+      !CanProfile(data_source->ds_config, new_connection->peer_uid())) {
+    PERFETTO_ELOG("%d (%s) is not profileable.", process.pid,
+                  process.cmdline.c_str());
+    return;
+  }
+
   uint64_t shmem_size = data_source->config.shmem_size_bytes();
   if (!shmem_size)
     shmem_size = kDefaultShmemSize;
@@ -953,21 +977,24 @@
   pending_processes_.emplace(peer_pid, std::move(pending_process));
 }
 
-void HeapprofdProducer::PostAllocRecord(std::vector<AllocRecord> alloc_recs) {
+void HeapprofdProducer::PostAllocRecord(
+    UnwindingWorker* worker,
+    std::unique_ptr<AllocRecord> alloc_rec) {
   // Once we can use C++14, this should be std::moved into the lambda instead.
-  std::vector<AllocRecord>* raw_alloc_recs =
-      new std::vector<AllocRecord>(std::move(alloc_recs));
+  auto* raw_alloc_rec = alloc_rec.release();
   auto weak_this = weak_factory_.GetWeakPtr();
-  task_runner_->PostTask([weak_this, raw_alloc_recs] {
+  task_runner_->PostTask([weak_this, raw_alloc_rec, worker] {
+    std::unique_ptr<AllocRecord> unique_alloc_ref =
+        std::unique_ptr<AllocRecord>(raw_alloc_rec);
     if (weak_this) {
-      for (AllocRecord& alloc_rec : *raw_alloc_recs)
-        weak_this->HandleAllocRecord(std::move(alloc_rec));
+      weak_this->HandleAllocRecord(unique_alloc_ref.get());
+      worker->ReturnAllocRecord(std::move(unique_alloc_ref));
     }
-    delete raw_alloc_recs;
   });
 }
 
-void HeapprofdProducer::PostFreeRecord(std::vector<FreeRecord> free_recs) {
+void HeapprofdProducer::PostFreeRecord(UnwindingWorker*,
+                                       std::vector<FreeRecord> free_recs) {
   // Once we can use C++14, this should be std::moved into the lambda instead.
   std::vector<FreeRecord>* raw_free_recs =
       new std::vector<FreeRecord>(std::move(free_recs));
@@ -981,7 +1008,17 @@
   });
 }
 
-void HeapprofdProducer::PostSocketDisconnected(DataSourceInstanceID ds_id,
+void HeapprofdProducer::PostHeapNameRecord(UnwindingWorker*,
+                                           HeapNameRecord rec) {
+  auto weak_this = weak_factory_.GetWeakPtr();
+  task_runner_->PostTask([weak_this, rec] {
+    if (weak_this)
+      weak_this->HandleHeapNameRecord(rec);
+  });
+}
+
+void HeapprofdProducer::PostSocketDisconnected(UnwindingWorker*,
+                                               DataSourceInstanceID ds_id,
                                                pid_t pid,
                                                SharedRingBuffer::Stats stats) {
   auto weak_this = weak_factory_.GetWeakPtr();
@@ -991,56 +1028,69 @@
   });
 }
 
-void HeapprofdProducer::HandleAllocRecord(AllocRecord alloc_rec) {
-  const AllocMetadata& alloc_metadata = alloc_rec.alloc_metadata;
-  auto it = data_sources_.find(alloc_rec.data_source_instance_id);
+void HeapprofdProducer::HandleAllocRecord(AllocRecord* alloc_rec) {
+  const AllocMetadata& alloc_metadata = alloc_rec->alloc_metadata;
+  auto it = data_sources_.find(alloc_rec->data_source_instance_id);
   if (it == data_sources_.end()) {
     PERFETTO_LOG("Invalid data source in alloc record.");
     return;
   }
 
   DataSource& ds = it->second;
-  auto process_state_it = ds.process_states.find(alloc_rec.pid);
+  auto process_state_it = ds.process_states.find(alloc_rec->pid);
   if (process_state_it == ds.process_states.end()) {
     PERFETTO_LOG("Invalid PID in alloc record.");
     return;
   }
 
+  if (ds.config.stream_allocations()) {
+    auto packet = ds.trace_writer->NewTracePacket();
+    auto* streaming_alloc = packet->set_streaming_allocation();
+    streaming_alloc->add_address(alloc_metadata.alloc_address);
+    streaming_alloc->add_size(alloc_metadata.alloc_size);
+    streaming_alloc->add_sample_size(alloc_metadata.sample_size);
+    streaming_alloc->add_clock_monotonic_coarse_timestamp(
+        alloc_metadata.clock_monotonic_coarse_timestamp);
+    streaming_alloc->add_heap_id(alloc_metadata.heap_id);
+    streaming_alloc->add_sequence_number(alloc_metadata.sequence_number);
+    return;
+  }
+
   const auto& prefixes = ds.config.skip_symbol_prefix();
   if (!prefixes.empty()) {
-    for (FrameData& frame_data : alloc_rec.frames) {
-      const std::string& map = frame_data.frame.map_name;
+    for (unwindstack::FrameData& frame_data : alloc_rec->frames) {
+      const std::string& map = frame_data.map_name;
       if (std::find_if(prefixes.cbegin(), prefixes.cend(),
                        [&map](const std::string& prefix) {
                          return base::StartsWith(map, prefix);
                        }) != prefixes.cend()) {
-        frame_data.frame.function_name = "FILTERED";
+        frame_data.function_name = "FILTERED";
       }
     }
   }
 
   ProcessState& process_state = process_state_it->second;
   HeapTracker& heap_tracker =
-      process_state.GetHeapTracker(alloc_rec.alloc_metadata.heap_id);
+      process_state.GetHeapTracker(alloc_rec->alloc_metadata.heap_id);
 
-  if (alloc_rec.error)
+  if (alloc_rec->error)
     process_state.unwinding_errors++;
-  if (alloc_rec.reparsed_map)
+  if (alloc_rec->reparsed_map)
     process_state.map_reparses++;
   process_state.heap_samples++;
-  process_state.unwinding_time_us.Add(alloc_rec.unwinding_time_us);
-  process_state.total_unwinding_time_us += alloc_rec.unwinding_time_us;
+  process_state.unwinding_time_us.Add(alloc_rec->unwinding_time_us);
+  process_state.total_unwinding_time_us += alloc_rec->unwinding_time_us;
 
   // abspc may no longer refer to the same functions, as we had to reparse
   // maps. Reset the cache.
-  if (alloc_rec.reparsed_map)
+  if (alloc_rec->reparsed_map)
     heap_tracker.ClearFrameCache();
 
-  heap_tracker.RecordMalloc(alloc_rec.frames, alloc_metadata.alloc_address,
-                            alloc_metadata.sample_size,
-                            alloc_metadata.alloc_size,
-                            alloc_metadata.sequence_number,
-                            alloc_metadata.clock_monotonic_coarse_timestamp);
+  heap_tracker.RecordMalloc(
+      alloc_rec->frames, alloc_rec->build_ids, alloc_metadata.alloc_address,
+      alloc_metadata.sample_size, alloc_metadata.alloc_size,
+      alloc_metadata.sequence_number,
+      alloc_metadata.clock_monotonic_coarse_timestamp);
 }
 
 void HeapprofdProducer::HandleFreeRecord(FreeRecord free_rec) {
@@ -1057,6 +1107,15 @@
     return;
   }
 
+  if (ds.config.stream_allocations()) {
+    auto packet = ds.trace_writer->NewTracePacket();
+    auto* streaming_free = packet->set_streaming_free();
+    streaming_free->add_address(free_rec.entry.addr);
+    streaming_free->add_heap_id(free_rec.entry.heap_id);
+    streaming_free->add_sequence_number(free_rec.entry.sequence_number);
+    return;
+  }
+
   ProcessState& process_state = process_state_it->second;
 
   const FreeEntry& entry = free_rec.entry;
@@ -1064,6 +1123,39 @@
   heap_tracker.RecordFree(entry.addr, entry.sequence_number, 0);
 }
 
+void HeapprofdProducer::HandleHeapNameRecord(HeapNameRecord rec) {
+  auto it = data_sources_.find(rec.data_source_instance_id);
+  if (it == data_sources_.end()) {
+    PERFETTO_LOG("Invalid data source in free record.");
+    return;
+  }
+
+  DataSource& ds = it->second;
+  auto process_state_it = ds.process_states.find(rec.pid);
+  if (process_state_it == ds.process_states.end()) {
+    PERFETTO_LOG("Invalid PID in free record.");
+    return;
+  }
+
+  ProcessState& process_state = process_state_it->second;
+  const HeapName& entry = rec.entry;
+  std::string heap_name = entry.heap_name;
+  if (heap_name.empty()) {
+    PERFETTO_ELOG("Ignoring empty heap name.");
+    return;
+  }
+  if (entry.heap_id == 0) {
+    PERFETTO_ELOG("Invalid zero heap ID.");
+    return;
+  }
+  std::string& existing_heap_name = process_state.heap_names[entry.heap_id];
+  if (!existing_heap_name.empty() && existing_heap_name != heap_name) {
+    PERFETTO_ELOG("Overriding heap name %s with %s", existing_heap_name.c_str(),
+                  heap_name.c_str());
+  }
+  existing_heap_name = entry.heap_name;
+}
+
 void HeapprofdProducer::TerminateWhenDone() {
   if (data_sources_.empty())
     TerminateProcess(0);
@@ -1116,6 +1208,7 @@
   ProcessState& process_state = process_state_it->second;
   process_state.disconnected = !ds.shutting_down;
   process_state.buffer_overran = stats.hit_timeout;
+  process_state.client_spinlock_blocked_us = stats.client_spinlock_blocked_us;
   process_state.buffer_corrupted =
       stats.num_writes_corrupt > 0 || stats.num_reads_corrupt > 0;
 
@@ -1124,94 +1217,100 @@
   MaybeFinishDataSource(&ds);
 }
 
-void HeapprofdProducer::CheckDataSourceCpu() {
+void HeapprofdProducer::CheckDataSourceCpuTask() {
   auto weak_producer = weak_factory_.GetWeakPtr();
   task_runner_->PostDelayedTask(
       [weak_producer] {
         if (!weak_producer)
           return;
-        weak_producer->CheckDataSourceCpu();
+        weak_producer->CheckDataSourceCpuTask();
       },
       kGuardrailIntervalMs);
 
-  bool any_guardrail = false;
-  for (auto& id_and_ds : data_sources_) {
-    DataSource& ds = id_and_ds.second;
-    if (ds.config.max_heapprofd_cpu_secs() > 0)
-      any_guardrail = true;
-  }
-
-  if (!any_guardrail)
-    return;
-
-  base::Optional<uint64_t> cputime_sec = GetCputimeSec();
-  if (!cputime_sec) {
-    PERFETTO_ELOG("Failed to get CPU time.");
-    return;
-  }
-
-  for (auto& id_and_ds : data_sources_) {
-    DataSource& ds = id_and_ds.second;
-    uint64_t ds_max_cpu = ds.config.max_heapprofd_cpu_secs();
-    if (ds_max_cpu > 0) {
-      // We reject data-sources with CPU guardrails if we cannot read the
-      // initial value.
-      PERFETTO_CHECK(ds.start_cputime_sec);
-      uint64_t cpu_diff = *cputime_sec - *ds.start_cputime_sec;
-      if (*cputime_sec > *ds.start_cputime_sec && cpu_diff > ds_max_cpu) {
-        PERFETTO_ELOG(
-            "Exceeded data-source CPU guardrail "
-            "(%" PRIu64 " > %" PRIu64 "). Shutting down.",
-            cpu_diff, ds_max_cpu);
-        ds.hit_guardrail = true;
-        ShutdownDataSource(&ds);
-      }
-    }
-  }
-}
-
-void HeapprofdProducer::CheckDataSourceMemory() {
-  auto weak_producer = weak_factory_.GetWeakPtr();
-  task_runner_->PostDelayedTask(
-      [weak_producer] {
-        if (!weak_producer)
-          return;
-        weak_producer->CheckDataSourceMemory();
-      },
-      kGuardrailIntervalMs);
-
-  bool any_guardrail = false;
-  for (auto& id_and_ds : data_sources_) {
-    DataSource& ds = id_and_ds.second;
-    if (ds.config.max_heapprofd_memory_kb() > 0)
-      any_guardrail = true;
-  }
-
-  if (!any_guardrail)
-    return;
-
-  base::Optional<uint32_t> anon_and_swap;
-  base::Optional<std::string> status = ReadStatus(getpid());
-  if (status)
-    anon_and_swap = GetRssAnonAndSwap(*status);
-
-  if (!anon_and_swap) {
-    PERFETTO_ELOG("Failed to read heapprofd memory.");
-    return;
-  }
-
-  for (auto& id_and_ds : data_sources_) {
-    DataSource& ds = id_and_ds.second;
-    uint32_t ds_max_mem = ds.config.max_heapprofd_memory_kb();
-    if (ds_max_mem > 0 && *anon_and_swap > ds_max_mem) {
-      PERFETTO_ELOG("Exceeded data-source memory guardrail (%" PRIu32
-                    " > %" PRIu32 "). Shutting down.",
-                    *anon_and_swap, ds_max_mem);
+  ProfilerCpuGuardrails gr;
+  for (auto& p : data_sources_) {
+    DataSource& ds = p.second;
+    if (gr.IsOverCpuThreshold(ds.guardrail_config)) {
       ds.hit_guardrail = true;
       ShutdownDataSource(&ds);
     }
   }
 }
 
+void HeapprofdProducer::CheckDataSourceMemoryTask() {
+  auto weak_producer = weak_factory_.GetWeakPtr();
+  task_runner_->PostDelayedTask(
+      [weak_producer] {
+        if (!weak_producer)
+          return;
+        weak_producer->CheckDataSourceMemoryTask();
+      },
+      kGuardrailIntervalMs);
+  ProfilerMemoryGuardrails gr;
+  for (auto& p : data_sources_) {
+    DataSource& ds = p.second;
+    if (gr.IsOverMemoryThreshold(ds.guardrail_config)) {
+      ds.hit_guardrail = true;
+      ShutdownDataSource(&ds);
+    }
+  }
+}
+
+bool CanProfile(const DataSourceConfig& ds_config, uint64_t uid) {
+#if !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+  base::ignore_result(ds_config);
+  base::ignore_result(uid);
+  return true;
+#else
+  char buf[PROP_VALUE_MAX + 1] = {};
+  int ret = __system_property_get("ro.build.type", buf);
+  PERFETTO_CHECK(ret >= 0);
+  return CanProfileAndroid(ds_config, uid, std::string(buf),
+                           "/data/system/packages.list");
+#endif
+}
+
+bool CanProfileAndroid(const DataSourceConfig& ds_config,
+                       uint64_t uid,
+                       const std::string& build_type,
+                       const std::string& packages_list_path) {
+  // These are replicated constants from libcutils android_filesystem_config.h
+  constexpr auto kAidAppStart = 10000;     // AID_APP_START
+  constexpr auto kAidAppEnd = 19999;       // AID_APP_END
+  constexpr auto kAidUserOffset = 100000;  // AID_USER_OFFSET
+
+  if (build_type != "user") {
+    return true;
+  }
+
+  if (ds_config.enable_extra_guardrails()) {
+    return false;  // no extra guardrails on user builds.
+  }
+
+  uint64_t uid_without_profile = uid % kAidUserOffset;
+  if (uid_without_profile < kAidAppStart || kAidAppEnd < uid_without_profile) {
+    // TODO(fmayer): relax this.
+    return false;  // no native services on user.
+  }
+
+  std::string content;
+  if (!base::ReadFile(packages_list_path, &content)) {
+    PERFETTO_ELOG("Failed to read %s.", packages_list_path.c_str());
+    return false;
+  }
+  for (base::StringSplitter ss(std::move(content), '\n'); ss.Next();) {
+    Package pkg;
+    if (!ReadPackagesListLine(ss.cur_token(), &pkg)) {
+      PERFETTO_ELOG("Failed to parse packages.list.");
+      return false;
+    }
+    if (pkg.uid == uid_without_profile &&
+        (pkg.profileable_from_shell || pkg.debuggable)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/heapprofd_producer.h b/src/profiling/memory/heapprofd_producer.h
index 87f60bc..aa11ab7 100644
--- a/src/profiling/memory/heapprofd_producer.h
+++ b/src/profiling/memory/heapprofd_producer.h
@@ -22,6 +22,8 @@
 #include <map>
 #include <vector>
 
+#include <inttypes.h>
+
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/unix_socket.h"
@@ -33,13 +35,15 @@
 #include "perfetto/ext/tracing/core/tracing_service.h"
 #include "perfetto/tracing/core/data_source_config.h"
 
+#include "perfetto/tracing/core/forward_decls.h"
 #include "src/profiling/common/interning_output.h"
 #include "src/profiling/common/proc_utils.h"
+#include "src/profiling/common/profiler_guardrails.h"
 #include "src/profiling/memory/bookkeeping.h"
 #include "src/profiling/memory/bookkeeping_dump.h"
-#include "src/profiling/memory/page_idle_checker.h"
 #include "src/profiling/memory/system_property.h"
 #include "src/profiling/memory/unwinding.h"
+#include "src/profiling/memory/unwound_messages.h"
 
 #include "protos/perfetto/config/profiling/heapprofd_config.gen.h"
 
@@ -72,10 +76,16 @@
 // clients. This can be implemented as an additional mode here.
 enum class HeapprofdMode { kCentral, kChild };
 
-void HeapprofdConfigToClientConfiguration(
+bool HeapprofdConfigToClientConfiguration(
     const HeapprofdConfig& heapprofd_config,
     ClientConfiguration* cli_config);
 
+bool CanProfile(const DataSourceConfig& ds_config, uint64_t uid);
+bool CanProfileAndroid(const DataSourceConfig& ds_config,
+                       uint64_t uid,
+                       const std::string& build_type,
+                       const std::string& packages_list_path);
+
 // Heap profiling producer. Can be instantiated in two modes, central and
 // child (also referred to as fork mode).
 //
@@ -103,7 +113,8 @@
   // Alternatively, find a better name for this.
   class SocketDelegate : public base::UnixSocket::EventListener {
    public:
-    SocketDelegate(HeapprofdProducer* producer) : producer_(producer) {}
+    explicit SocketDelegate(HeapprofdProducer* producer)
+        : producer_(producer) {}
 
     void OnDisconnect(base::UnixSocket* self) override;
     void OnNewIncomingConnection(
@@ -138,14 +149,17 @@
   void DumpAll();
 
   // UnwindingWorker::Delegate impl:
-  void PostAllocRecord(std::vector<AllocRecord>) override;
-  void PostFreeRecord(std::vector<FreeRecord>) override;
-  void PostSocketDisconnected(DataSourceInstanceID,
+  void PostAllocRecord(UnwindingWorker*, std::unique_ptr<AllocRecord>) override;
+  void PostFreeRecord(UnwindingWorker*, std::vector<FreeRecord>) override;
+  void PostHeapNameRecord(UnwindingWorker*, HeapNameRecord) override;
+  void PostSocketDisconnected(UnwindingWorker*,
+                              DataSourceInstanceID,
                               pid_t,
                               SharedRingBuffer::Stats) override;
 
-  void HandleAllocRecord(AllocRecord);
+  void HandleAllocRecord(AllocRecord*);
   void HandleFreeRecord(FreeRecord);
+  void HandleHeapNameRecord(HeapNameRecord);
   void HandleSocketDisconnected(DataSourceInstanceID,
                                 pid_t,
                                 SharedRingBuffer::Stats);
@@ -196,12 +210,13 @@
     uint64_t unwinding_errors = 0;
 
     uint64_t total_unwinding_time_us = 0;
+    uint64_t client_spinlock_blocked_us = 0;
     GlobalCallstackTrie* callsites;
     bool dump_at_max_mode;
     LogHistogram unwinding_time_us;
     std::map<uint32_t, HeapTracker> heap_trackers;
+    std::map<uint32_t, std::string> heap_names;
 
-    base::Optional<PageIdleChecker> page_idle_checker;
     HeapTracker& GetHeapTracker(uint32_t heap_id) {
       auto it = heap_trackers.find(heap_id);
       if (it == heap_trackers.end()) {
@@ -214,13 +229,15 @@
   };
 
   struct DataSource {
-    DataSource(std::unique_ptr<TraceWriter> tw) : trace_writer(std::move(tw)) {
+    explicit DataSource(std::unique_ptr<TraceWriter> tw)
+        : trace_writer(std::move(tw)) {
       // Make MSAN happy.
       memset(&client_configuration, 0, sizeof(client_configuration));
     }
 
     DataSourceInstanceID id;
     std::unique_ptr<TraceWriter> trace_writer;
+    DataSourceConfig ds_config;
     HeapprofdConfig config;
     ClientConfiguration client_configuration;
     std::vector<SystemProperties::Handle> properties;
@@ -234,7 +251,7 @@
     bool hit_guardrail = false;
     bool was_stopped = false;
     uint32_t stop_timeout_ms;
-    base::Optional<uint64_t> start_cputime_sec;
+    GuardrailConfig guardrail_config;
   };
 
   struct PendingProcess {
@@ -251,13 +268,11 @@
   void ResetConnectionBackoff();
   void IncreaseConnectionBackoff();
 
-  base::Optional<uint64_t> GetCputimeSec();
-
-  void CheckDataSourceMemory();
-  void CheckDataSourceCpu();
+  void CheckDataSourceMemoryTask();
+  void CheckDataSourceCpuTask();
 
   void FinishDataSourceFlush(FlushRequestID flush_id);
-  bool DumpProcessesInDataSource(DataSourceInstanceID id);
+  void DumpProcessesInDataSource(DataSource* ds);
   void DumpProcessState(DataSource* ds, pid_t pid, ProcessState* process);
 
   void DoContinuousDump(DataSourceInstanceID id, uint32_t dump_interval);
@@ -278,6 +293,8 @@
   void ShutdownDataSource(DataSource* ds);
   bool MaybeFinishDataSource(DataSource* ds);
 
+  void WriteRejectedConcurrentSession(BufferID buffer_id, pid_t pid);
+
   // Class state:
 
   // Task runner is owned by the main thread.
@@ -322,7 +339,6 @@
   base::Optional<std::function<void()>> data_source_callback_;
 
   SocketDelegate socket_delegate_;
-  base::ScopedFile stat_fd_;
 
   base::WeakPtrFactory<HeapprofdProducer> weak_factory_;  // Keep last.
 };
diff --git a/src/profiling/memory/heapprofd_producer_unittest.cc b/src/profiling/memory/heapprofd_producer_unittest.cc
index 01b6155..f9fcc9d 100644
--- a/src/profiling/memory/heapprofd_producer_unittest.cc
+++ b/src/profiling/memory/heapprofd_producer_unittest.cc
@@ -16,6 +16,8 @@
 
 #include "src/profiling/memory/heapprofd_producer.h"
 
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/commit_data_request.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
@@ -85,20 +87,20 @@
   cfg.add_heaps("foo");
   cfg.set_sampling_interval_bytes(4096);
   ClientConfiguration cli_config;
-  HeapprofdConfigToClientConfiguration(cfg, &cli_config);
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
   EXPECT_EQ(cli_config.num_heaps, 1u);
-  EXPECT_EQ(cli_config.interval, 4096u);
-  EXPECT_STREQ(cli_config.heaps[0], "foo");
+  EXPECT_STREQ(cli_config.heaps[0].name, "foo");
+  EXPECT_EQ(cli_config.heaps[0].interval, 4096u);
 }
 
 TEST(HeapprofdConfigToClientConfigurationTest, DefaultHeap) {
   HeapprofdConfig cfg;
   cfg.set_sampling_interval_bytes(4096);
   ClientConfiguration cli_config;
-  HeapprofdConfigToClientConfiguration(cfg, &cli_config);
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
   EXPECT_EQ(cli_config.num_heaps, 1u);
-  EXPECT_EQ(cli_config.interval, 4096u);
-  EXPECT_STREQ(cli_config.heaps[0], "malloc");
+  EXPECT_STREQ(cli_config.heaps[0].name, "libc.malloc");
+  EXPECT_EQ(cli_config.heaps[0].interval, 4096u);
 }
 
 TEST(HeapprofdConfigToClientConfigurationTest, TwoHeaps) {
@@ -107,29 +109,157 @@
   cfg.add_heaps("bar");
   cfg.set_sampling_interval_bytes(4096);
   ClientConfiguration cli_config;
-  HeapprofdConfigToClientConfiguration(cfg, &cli_config);
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
   EXPECT_EQ(cli_config.num_heaps, 2u);
-  EXPECT_EQ(cli_config.interval, 4096u);
-  EXPECT_STREQ(cli_config.heaps[0], "foo");
-  EXPECT_STREQ(cli_config.heaps[1], "bar");
+  EXPECT_STREQ(cli_config.heaps[0].name, "foo");
+  EXPECT_STREQ(cli_config.heaps[1].name, "bar");
+  EXPECT_EQ(cli_config.heaps[0].interval, 4096u);
+  EXPECT_EQ(cli_config.heaps[1].interval, 4096u);
+}
+
+TEST(HeapprofdConfigToClientConfigurationTest, TwoHeapsIntervals) {
+  HeapprofdConfig cfg;
+  cfg.add_heaps("foo");
+  cfg.add_heap_sampling_intervals(4096u);
+  cfg.add_heaps("bar");
+  cfg.add_heap_sampling_intervals(1u);
+  ClientConfiguration cli_config;
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
+  EXPECT_EQ(cli_config.num_heaps, 2u);
+  EXPECT_STREQ(cli_config.heaps[0].name, "foo");
+  EXPECT_STREQ(cli_config.heaps[1].name, "bar");
+  EXPECT_EQ(cli_config.heaps[0].interval, 4096u);
+  EXPECT_EQ(cli_config.heaps[1].interval, 1u);
 }
 
 TEST(HeapprofdConfigToClientConfigurationTest, OverflowHeapName) {
+  std::string large_name(100, 'a');
   HeapprofdConfig cfg;
-  cfg.add_heaps("foooooooooooooooooooooooooooooooooooooooooooooo");
+  cfg.add_heaps(large_name);
+  cfg.set_sampling_interval_bytes(1);
   ClientConfiguration cli_config;
-  HeapprofdConfigToClientConfiguration(cfg, &cli_config);
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
   EXPECT_EQ(cli_config.num_heaps, 0u);
 }
 
 TEST(HeapprofdConfigToClientConfigurationTest, OverflowHeapNameAndValid) {
+  std::string large_name(100, 'a');
   HeapprofdConfig cfg;
-  cfg.add_heaps("foooooooooooooooooooooooooooooooooooooooooooooo");
+  cfg.add_heaps(large_name);
   cfg.add_heaps("foo");
+  cfg.set_sampling_interval_bytes(1);
   ClientConfiguration cli_config;
-  HeapprofdConfigToClientConfiguration(cfg, &cli_config);
+  ASSERT_TRUE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
   EXPECT_EQ(cli_config.num_heaps, 1u);
-  EXPECT_STREQ(cli_config.heaps[0], "foo");
+  EXPECT_STREQ(cli_config.heaps[0].name, "foo");
+}
+
+TEST(HeapprofdConfigToClientConfigurationTest, ZeroSampling) {
+  HeapprofdConfig cfg;
+  cfg.add_heaps("foo");
+  cfg.set_sampling_interval_bytes(0);
+  ClientConfiguration cli_config;
+  EXPECT_FALSE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
+}
+
+TEST(HeapprofdConfigToClientConfigurationTest, ZeroSamplingMultiple) {
+  HeapprofdConfig cfg;
+  cfg.add_heaps("foo");
+  cfg.add_heap_sampling_intervals(4096u);
+  cfg.add_heaps("bar");
+  cfg.add_heap_sampling_intervals(0);
+  ClientConfiguration cli_config;
+  EXPECT_FALSE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
+}
+
+TEST(CanProfileAndroidTest, NonUserSystemExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 1, "userdebug", "/dev/null"));
+}
+
+TEST(CanProfileAndroidTest, NonUserNonProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "userdebug", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, NonUserNonProfileableAppExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "userdebug", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableAppExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_FALSE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableAppMultiuser) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 210001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserNonProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_FALSE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserDebuggableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 1 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
 }
 
 }  // namespace profiling
diff --git a/src/profiling/memory/heapprofd_standalone_client_example.cc b/src/profiling/memory/heapprofd_standalone_client_example.cc
index 8cdc400..5e18f4b 100644
--- a/src/profiling/memory/heapprofd_standalone_client_example.cc
+++ b/src/profiling/memory/heapprofd_standalone_client_example.cc
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
 
 #include <unistd.h>
 
 int main(int, char**) {
-  HeapprofdHeapInfo info{"test", nullptr};
-  uint32_t heap_id = heapprofd_register_heap(&info, sizeof(info));
+  uint32_t heap_id = AHeapProfile_registerHeap(AHeapInfo_create("test"));
   for (uint64_t i = 0; i < 100000; ++i) {
-    heapprofd_report_allocation(heap_id, i, i);
+    AHeapProfile_reportAllocation(heap_id, i, i);
     sleep(1);
   }
 }
diff --git a/src/profiling/memory/java_hprof_producer.cc b/src/profiling/memory/java_hprof_producer.cc
index 4d015c2..65bab10 100644
--- a/src/profiling/memory/java_hprof_producer.cc
+++ b/src/profiling/memory/java_hprof_producer.cc
@@ -17,6 +17,7 @@
 #include "src/profiling/memory/java_hprof_producer.h"
 
 #include <signal.h>
+#include <limits>
 
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
@@ -55,8 +56,11 @@
   const std::set<pid_t>& pids = ds.pids;
   for (pid_t pid : pids) {
     PERFETTO_DLOG("Sending %d to %d", kJavaHeapprofdSignal, pid);
-    if (kill(pid, kJavaHeapprofdSignal) != 0) {
-      PERFETTO_DPLOG("kill");
+    union sigval signal_value;
+    signal_value.sival_int = static_cast<int32_t>(
+        ds.tracing_session_id % std::numeric_limits<int32_t>::max());
+    if (sigqueue(pid, kJavaHeapprofdSignal, signal_value) != 0) {
+      PERFETTO_DPLOG("sigqueue");
     }
   }
 }
@@ -94,6 +98,7 @@
     RemoveUnderAnonThreshold(config.min_anonymous_memory_kb(), &ds.pids);
 
   ds.config = std::move(config);
+  ds.tracing_session_id = ds_config.tracing_session_id();
   data_sources_.emplace(id, std::move(ds));
 }
 
diff --git a/src/profiling/memory/java_hprof_producer.h b/src/profiling/memory/java_hprof_producer.h
index d584f44..2c3aa07 100644
--- a/src/profiling/memory/java_hprof_producer.h
+++ b/src/profiling/memory/java_hprof_producer.h
@@ -23,6 +23,7 @@
 
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/producer.h"
 #include "perfetto/ext/tracing/core/tracing_service.h"
 #include "perfetto/ext/tracing/ipc/producer_ipc_client.h"
@@ -38,7 +39,7 @@
 
 class JavaHprofProducer : public Producer {
  public:
-  JavaHprofProducer(base::TaskRunner* task_runner)
+  explicit JavaHprofProducer(base::TaskRunner* task_runner)
       : task_runner_(task_runner), weak_factory_(this) {}
 
   // Producer Impl:
@@ -68,6 +69,7 @@
   };
 
   struct DataSource {
+    TracingSessionID tracing_session_id;
     DataSourceInstanceID id;
     std::set<pid_t> pids;
     JavaHprofConfig config;
diff --git a/src/profiling/memory/main.cc b/src/profiling/memory/main.cc
index 103c9e8..cfc4943 100644
--- a/src/profiling/memory/main.cc
+++ b/src/profiling/memory/main.cc
@@ -67,7 +67,7 @@
   base::ScopedFile inherited_sock_fd;
 
   enum { kCleanupCrash = 256, kTargetPid, kTargetCmd, kInheritFd };
-  static struct option long_options[] = {
+  static option long_options[] = {
       {"cleanup-after-crash", no_argument, nullptr, kCleanupCrash},
       {"exclusive-for-pid", required_argument, nullptr, kTargetPid},
       {"exclusive-for-cmdline", required_argument, nullptr, kTargetCmd},
diff --git a/src/profiling/memory/malloc_hooks.cc b/src/profiling/memory/malloc_hooks.cc
index 6a1fcc0..6ba7e79 100644
--- a/src/profiling/memory/malloc_hooks.cc
+++ b/src/profiling/memory/malloc_hooks.cc
@@ -23,60 +23,51 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
 
-// This is so we can make an so that we can swap out with the existing
-// libc_malloc_hooks.so
-#ifndef HEAPPROFD_PREFIX
-#define HEAPPROFD_PREFIX heapprofd
-#endif
-
-#define HEAPPROFD_ADD_PREFIX(name) \
-  PERFETTO_BUILDFLAG_CAT(HEAPPROFD_PREFIX, name)
+#include "src/profiling/memory/wrap_allocators.h"
 
 #pragma GCC visibility push(default)
 extern "C" {
 
-bool HEAPPROFD_ADD_PREFIX(_initialize)(const MallocDispatch* malloc_dispatch,
-                                       bool* zygote_child,
-                                       const char* options);
-void HEAPPROFD_ADD_PREFIX(_finalize)();
-void HEAPPROFD_ADD_PREFIX(_dump_heap)(const char* file_name);
-void HEAPPROFD_ADD_PREFIX(_get_malloc_leak_info)(uint8_t** info,
-                                                 size_t* overall_size,
-                                                 size_t* info_size,
-                                                 size_t* total_memory,
-                                                 size_t* backtrace_size);
-bool HEAPPROFD_ADD_PREFIX(_write_malloc_leak_info)(FILE* fp);
-ssize_t HEAPPROFD_ADD_PREFIX(_malloc_backtrace)(void* pointer,
-                                                uintptr_t* frames,
-                                                size_t frame_count);
-void HEAPPROFD_ADD_PREFIX(_free_malloc_leak_info)(uint8_t* info);
-size_t HEAPPROFD_ADD_PREFIX(_malloc_usable_size)(void* pointer);
-void* HEAPPROFD_ADD_PREFIX(_malloc)(size_t size);
-void HEAPPROFD_ADD_PREFIX(_free)(void* pointer);
-void* HEAPPROFD_ADD_PREFIX(_aligned_alloc)(size_t alignment, size_t size);
-void* HEAPPROFD_ADD_PREFIX(_memalign)(size_t alignment, size_t bytes);
-void* HEAPPROFD_ADD_PREFIX(_realloc)(void* pointer, size_t bytes);
-void* HEAPPROFD_ADD_PREFIX(_calloc)(size_t nmemb, size_t bytes);
-struct mallinfo HEAPPROFD_ADD_PREFIX(_mallinfo)();
-int HEAPPROFD_ADD_PREFIX(_mallopt)(int param, int value);
-int HEAPPROFD_ADD_PREFIX(_malloc_info)(int options, FILE* fp);
-int HEAPPROFD_ADD_PREFIX(_posix_memalign)(void** memptr,
-                                          size_t alignment,
-                                          size_t size);
-int HEAPPROFD_ADD_PREFIX(_malloc_iterate)(uintptr_t base,
-                                          size_t size,
-                                          void (*callback)(uintptr_t base,
-                                                           size_t size,
-                                                           void* arg),
-                                          void* arg);
-void HEAPPROFD_ADD_PREFIX(_malloc_disable)();
-void HEAPPROFD_ADD_PREFIX(_malloc_enable)();
+bool heapprofd_initialize(const MallocDispatch* malloc_dispatch,
+                          bool* zygote_child,
+                          const char* options);
+void heapprofd_finalize();
+void heapprofd_dump_heap(const char* file_name);
+void heapprofd_get_malloc_leak_info(uint8_t** info,
+                                    size_t* overall_size,
+                                    size_t* info_size,
+                                    size_t* total_memory,
+                                    size_t* backtrace_size);
+bool heapprofd_write_malloc_leak_info(FILE* fp);
+ssize_t heapprofd_malloc_backtrace(void* pointer,
+                                   uintptr_t* frames,
+                                   size_t frame_count);
+void heapprofd_free_malloc_leak_info(uint8_t* info);
+size_t heapprofd_malloc_usable_size(void* pointer);
+void* heapprofd_malloc(size_t size);
+void heapprofd_free(void* pointer);
+void* heapprofd_aligned_alloc(size_t alignment, size_t size);
+void* heapprofd_memalign(size_t alignment, size_t bytes);
+void* heapprofd_realloc(void* pointer, size_t bytes);
+void* heapprofd_calloc(size_t nmemb, size_t bytes);
+struct mallinfo heapprofd_mallinfo();
+int heapprofd_mallopt(int param, int value);
+int heapprofd_malloc_info(int options, FILE* fp);
+int heapprofd_posix_memalign(void** memptr, size_t alignment, size_t size);
+int heapprofd_malloc_iterate(uintptr_t base,
+                             size_t size,
+                             void (*callback)(uintptr_t base,
+                                              size_t size,
+                                              void* arg),
+                             void* arg);
+void heapprofd_malloc_disable();
+void heapprofd_malloc_enable();
 
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-void* HEAPPROFD_ADD_PREFIX(_pvalloc)(size_t bytes);
-void* HEAPPROFD_ADD_PREFIX(_valloc)(size_t size);
+void* heapprofd_pvalloc(size_t bytes);
+void* heapprofd_valloc(size_t size);
 #endif
 }
 #pragma GCC visibility pop
@@ -98,15 +89,15 @@
 // heapprofd_initialize. Concurrent calls get discarded, which might be our
 // unpatching attempt if there is a concurrent re-initialization running due to
 // a new signal.
-void ProfileCallback(bool enabled) {
-  if (!enabled) {
-    if (!android_mallopt(M_RESET_HOOKS, nullptr, 0))
-      PERFETTO_PLOG("Unpatching heapprofd hooks failed.");
-  }
+void ProfileDisabledCallback(void*, const AHeapProfileDisableCallbackInfo*) {
+  if (!android_mallopt(M_RESET_HOOKS, nullptr, 0))
+    PERFETTO_PLOG("Unpatching heapprofd hooks failed.");
 }
 
-HeapprofdHeapInfo info{"malloc", ProfileCallback};
-uint32_t g_heap_id = heapprofd_register_heap(&info, sizeof(info));
+uint32_t g_heap_id = AHeapProfile_registerHeap(
+    AHeapInfo_setDisabledCallback(AHeapInfo_create("libc.malloc"),
+                                  ProfileDisabledCallback,
+                                  nullptr));
 
 }  // namespace
 
@@ -120,82 +111,49 @@
 // Note: if profiling is triggered at runtime, this runs on a dedicated pthread
 // (which is safe to block). If profiling is triggered at startup, then this
 // code runs synchronously.
-bool HEAPPROFD_ADD_PREFIX(_initialize)(const MallocDispatch* malloc_dispatch,
-                                       bool*,
-                                       const char*) {
+bool heapprofd_initialize(const MallocDispatch* malloc_dispatch,
+                          bool*,
+                          const char*) {
   // Table of pointers to backing implementation.
   g_dispatch.store(malloc_dispatch);
-  return heapprofd_init_session(malloc_dispatch->malloc, malloc_dispatch->free);
+  return AHeapProfile_initSession(malloc_dispatch->malloc,
+                                  malloc_dispatch->free);
 }
 
-void HEAPPROFD_ADD_PREFIX(_finalize)() {
+void heapprofd_finalize() {
   // At the time of writing, invoked only as an atexit handler. We don't have
   // any specific action to take, and cleanup can be left to the OS.
 }
 
-void* HEAPPROFD_ADD_PREFIX(_malloc)(size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  void* addr = dispatch->malloc(size);
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(addr),
-                              size);
-  return addr;
+void* heapprofd_malloc(size_t size) {
+  return perfetto::profiling::wrap_malloc(g_heap_id, GetDispatch()->malloc,
+                                          size);
 }
 
-void* HEAPPROFD_ADD_PREFIX(_calloc)(size_t nmemb, size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  void* addr = dispatch->calloc(nmemb, size);
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(addr),
-                              nmemb * size);
-  return addr;
+void* heapprofd_calloc(size_t nmemb, size_t size) {
+  return perfetto::profiling::wrap_calloc(g_heap_id, GetDispatch()->calloc,
+                                          nmemb, size);
 }
 
-void* HEAPPROFD_ADD_PREFIX(_aligned_alloc)(size_t alignment, size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  void* addr = dispatch->aligned_alloc(alignment, size);
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(addr),
-                              size);
-  return addr;
+void* heapprofd_aligned_alloc(size_t alignment, size_t size) {
+  // aligned_alloc is the same as memalign.
+  return perfetto::profiling::wrap_memalign(
+      g_heap_id, GetDispatch()->aligned_alloc, alignment, size);
 }
 
-void* HEAPPROFD_ADD_PREFIX(_memalign)(size_t alignment, size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  void* addr = dispatch->memalign(alignment, size);
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(addr),
-                              size);
-  return addr;
+void* heapprofd_memalign(size_t alignment, size_t size) {
+  return perfetto::profiling::wrap_memalign(g_heap_id, GetDispatch()->memalign,
+                                            alignment, size);
 }
 
-int HEAPPROFD_ADD_PREFIX(_posix_memalign)(void** memptr,
-                                          size_t alignment,
-                                          size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  int res = dispatch->posix_memalign(memptr, alignment, size);
-  if (res != 0)
-    return res;
-
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(*memptr),
-                              size);
-  return 0;
+int heapprofd_posix_memalign(void** memptr, size_t alignment, size_t size) {
+  return perfetto::profiling::wrap_posix_memalign(
+      g_heap_id, GetDispatch()->posix_memalign, memptr, alignment, size);
 }
 
-// Note: we record the free before calling the backing implementation to make
-// sure that the address is not reused before we've processed the deallocation
-// (which includes assigning a sequence id to it).
-void HEAPPROFD_ADD_PREFIX(_free)(void* pointer) {
-  // free on a nullptr is valid but has no effect. Short circuit here, for
-  // various advantages:
-  // * More efficient
-  // * Notably printf calls free(nullptr) even when it is used in a way
-  //   malloc-free way, as it unconditionally frees the pointer even if
-  //   it was never written to.
-  //   Short circuiting here makes it less likely to accidentally build
-  //   infinite recursion.
-  if (pointer == nullptr)
-    return;
-
-  const MallocDispatch* dispatch = GetDispatch();
-  heapprofd_report_free(g_heap_id, reinterpret_cast<uint64_t>(pointer));
-  return dispatch->free(pointer);
+void heapprofd_free(void* pointer) {
+  return perfetto::profiling::wrap_free(g_heap_id, GetDispatch()->free,
+                                        pointer);
 }
 
 // Approach to recording realloc: under the initial lock, get a safe copy of the
@@ -206,80 +164,78 @@
 // As with the free, we record the deallocation before calling the backing
 // implementation to make sure the address is still exclusive while we're
 // processing it.
-void* HEAPPROFD_ADD_PREFIX(_realloc)(void* pointer, size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  if (pointer)
-    heapprofd_report_free(g_heap_id, reinterpret_cast<uint64_t>(pointer));
-  void* addr = dispatch->realloc(pointer, size);
-  heapprofd_report_allocation(g_heap_id, reinterpret_cast<uint64_t>(addr),
-                              size);
-  return addr;
+void* heapprofd_realloc(void* pointer, size_t size) {
+  return perfetto::profiling::wrap_realloc(g_heap_id, GetDispatch()->realloc,
+                                           pointer, size);
 }
 
-void HEAPPROFD_ADD_PREFIX(_dump_heap)(const char*) {}
+void heapprofd_dump_heap(const char*) {}
 
-void HEAPPROFD_ADD_PREFIX(
-    _get_malloc_leak_info)(uint8_t**, size_t*, size_t*, size_t*, size_t*) {}
+void heapprofd_get_malloc_leak_info(uint8_t**,
+                                    size_t*,
+                                    size_t*,
+                                    size_t*,
+                                    size_t*) {}
 
-bool HEAPPROFD_ADD_PREFIX(_write_malloc_leak_info)(FILE*) {
+bool heapprofd_write_malloc_leak_info(FILE*) {
   return false;
 }
 
-ssize_t HEAPPROFD_ADD_PREFIX(_malloc_backtrace)(void*, uintptr_t*, size_t) {
+ssize_t heapprofd_malloc_backtrace(void*, uintptr_t*, size_t) {
   return -1;
 }
 
-void HEAPPROFD_ADD_PREFIX(_free_malloc_leak_info)(uint8_t*) {}
+void heapprofd_free_malloc_leak_info(uint8_t*) {}
 
-size_t HEAPPROFD_ADD_PREFIX(_malloc_usable_size)(void* pointer) {
+size_t heapprofd_malloc_usable_size(void* pointer) {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->malloc_usable_size(pointer);
 }
 
-struct mallinfo HEAPPROFD_ADD_PREFIX(_mallinfo)() {
+struct mallinfo heapprofd_mallinfo() {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->mallinfo();
 }
 
-int HEAPPROFD_ADD_PREFIX(_mallopt)(int param, int value) {
+int heapprofd_mallopt(int param, int value) {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->mallopt(param, value);
 }
 
-int HEAPPROFD_ADD_PREFIX(_malloc_info)(int options, FILE* fp) {
+int heapprofd_malloc_info(int options, FILE* fp) {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->malloc_info(options, fp);
 }
 
-int HEAPPROFD_ADD_PREFIX(_malloc_iterate)(uintptr_t base,
-                                          size_t size,
-                                          void (*callback)(uintptr_t base,
-                                                   size_t size,
-                                                   void* arg),
-                                          void* arg) {
+int heapprofd_malloc_iterate(uintptr_t base,
+                             size_t size,
+                             void (*callback)(uintptr_t base,
+                                              size_t size,
+                                              void* arg),
+                             void* arg) {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->malloc_iterate(base, size, callback, arg);
 }
 
-void HEAPPROFD_ADD_PREFIX(_malloc_disable)() {
+void heapprofd_malloc_disable() {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->malloc_disable();
 }
 
-void HEAPPROFD_ADD_PREFIX(_malloc_enable)() {
+void heapprofd_malloc_enable() {
   const MallocDispatch* dispatch = GetDispatch();
   return dispatch->malloc_enable();
 }
 
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-void* HEAPPROFD_ADD_PREFIX(_pvalloc)(size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  return dispatch->pvalloc(size);
+void* heapprofd_pvalloc(size_t size) {
+  return perfetto::profiling::wrap_pvalloc(g_heap_id, GetDispatch()->pvalloc,
+                                           size);
 }
 
-void* HEAPPROFD_ADD_PREFIX(_valloc)(size_t size) {
-  const MallocDispatch* dispatch = GetDispatch();
-  return dispatch->valloc(size);
+void* heapprofd_valloc(size_t size) {
+  return perfetto::profiling::wrap_valloc(g_heap_id, GetDispatch()->valloc,
+                                          size);
 }
 
 #endif
diff --git a/src/profiling/memory/malloc_preload.cc b/src/profiling/memory/malloc_preload.cc
new file mode 100644
index 0000000..f911365
--- /dev/null
+++ b/src/profiling/memory/malloc_preload.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <malloc.h>
+#include <unistd.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/profiling/memory/heap_profile.h"
+#include "src/profiling/memory/wrap_allocators.h"
+
+namespace {
+// AHeapProfile_registerHeap is guaranteed to be safe to call from global
+// constructors.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wglobal-constructors"
+uint32_t g_heap_id = AHeapProfile_registerHeap(AHeapInfo_create("libc.malloc"));
+#pragma GCC diagnostic pop
+
+bool IsPowerOfTwo(size_t v) {
+  return (v != 0 && ((v & (v - 1)) == 0));
+}
+
+}  // namespace
+
+extern "C" {
+
+// These are exported by GLibc to be used by functions overwriting malloc
+// to call back to the real implementation.
+extern void* __libc_malloc(size_t);
+extern void __libc_free(void*);
+extern void* __libc_calloc(size_t, size_t);
+extern void* __libc_realloc(void*, size_t);
+extern void* __libc_memalign(size_t, size_t);
+extern void* __libc_pvalloc(size_t);
+extern void* __libc_valloc(size_t);
+extern void* __libc_reallocarray(void*, size_t, size_t);
+
+#pragma GCC visibility push(default)
+
+void* malloc(size_t size) {
+  return perfetto::profiling::wrap_malloc(g_heap_id, __libc_malloc, size);
+}
+
+void free(void* ptr) {
+  return perfetto::profiling::wrap_free(g_heap_id, __libc_free, ptr);
+}
+
+void* calloc(size_t nmemb, size_t size) {
+  return perfetto::profiling::wrap_calloc(g_heap_id, __libc_calloc, nmemb,
+                                          size);
+}
+
+void* realloc(void* ptr, size_t size) {
+  return perfetto::profiling::wrap_realloc(g_heap_id, __libc_realloc, ptr,
+                                           size);
+}
+
+int posix_memalign(void** memptr, size_t alignment, size_t size) {
+  if (alignment % sizeof(void*) || !IsPowerOfTwo(alignment / sizeof(void*))) {
+    return EINVAL;
+  }
+  void* alloc = perfetto::profiling::wrap_memalign(g_heap_id, __libc_memalign,
+                                                   alignment, size);
+  if (!alloc) {
+    return ENOMEM;
+  }
+  *memptr = alloc;
+  return 0;
+}
+
+void* aligned_alloc(size_t alignment, size_t size) {
+  return perfetto::profiling::wrap_memalign(g_heap_id, __libc_memalign,
+                                            alignment, size);
+}
+
+void* memalign(size_t alignment, size_t size) {
+  return perfetto::profiling::wrap_memalign(g_heap_id, __libc_memalign,
+                                            alignment, size);
+}
+
+void* pvalloc(size_t size) {
+  return perfetto::profiling::wrap_pvalloc(g_heap_id, __libc_pvalloc, size);
+}
+
+void* valloc(size_t size) {
+  return perfetto::profiling::wrap_valloc(g_heap_id, __libc_valloc, size);
+}
+
+void* reallocarray(void* ptr, size_t nmemb, size_t size) {
+  return perfetto::profiling::wrap_reallocarray(g_heap_id, __libc_reallocarray,
+                                                ptr, nmemb, size);
+}
+
+#pragma GCC visibility pop
+}
diff --git a/src/profiling/memory/page_idle_checker.cc b/src/profiling/memory/page_idle_checker.cc
deleted file mode 100644
index a5ac79e..0000000
--- a/src/profiling/memory/page_idle_checker.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/profiling/memory/page_idle_checker.h"
-#include "perfetto/ext/base/utils.h"
-
-#include <inttypes.h>
-#include <vector>
-
-namespace perfetto {
-namespace profiling {
-
-// TODO(fmayer): Be smarter about batching reads and writes to page_idle.
-
-int64_t PageIdleChecker::OnIdlePage(uint64_t addr, size_t size) {
-  uint64_t page_nr = addr / base::kPageSize;
-  uint64_t end_page_nr = (addr + size) / base::kPageSize;
-  // The trailing division will have rounded down, unless the end is at a page
-  // boundary. Add one page if we rounded down.
-  if ((addr + size) % base::kPageSize != 0)
-    end_page_nr++;
-
-  size_t pages = static_cast<size_t>(end_page_nr - page_nr);
-
-  int64_t idle_mem = 0;
-  for (size_t i = 0; i < pages; ++i) {
-    int idle = IsPageIdle(page_nr + i);
-    if (idle == -1)
-      continue;
-
-    if (idle) {
-      if (i == 0)
-        idle_mem += GetFirstPageShare(addr, size);
-      else if (i == pages - 1)
-        idle_mem += GetLastPageShare(addr, size);
-      else
-        idle_mem += base::kPageSize;
-    } else {
-      touched_virt_page_nrs_.emplace(page_nr + i);
-    }
-  }
-  return idle_mem;
-}
-
-void PageIdleChecker::MarkPagesIdle() {
-  for (uint64_t virt_page_nr : touched_virt_page_nrs_)
-    MarkPageIdle(virt_page_nr);
-  touched_virt_page_nrs_.clear();
-}
-
-void PageIdleChecker::MarkPageIdle(uint64_t virt_page_nr) {
-  // The file implements a bitmap where each bit corresponds to a memory page.
-  // The bitmap is represented by an array of 8-byte integers, and the page at
-  // PFN #i is mapped to bit #i%64 of array element #i/64, byte order i
-  // native. When a bit is set, the corresponding page is idle.
-  //
-  // The kernel ORs the value written with the existing bitmap, so we do not
-  // override previously written values.
-  // See https://www.kernel.org/doc/Documentation/vm/idle_page_tracking.txt
-  off64_t offset = 8 * (virt_page_nr / 64);
-  size_t bit_offset = virt_page_nr % 64;
-  uint64_t bit_pattern = 1 << bit_offset;
-  if (pwrite64(*page_idle_fd_, &bit_pattern, sizeof(bit_pattern), offset) !=
-      static_cast<ssize_t>(sizeof(bit_pattern))) {
-    PERFETTO_PLOG("Failed to write bit pattern at %" PRIi64 ".", offset);
-  }
-}
-
-int PageIdleChecker::IsPageIdle(uint64_t virt_page_nr) {
-  off64_t offset = 8 * (virt_page_nr / 64);
-  size_t bit_offset = virt_page_nr % 64;
-  uint64_t bit_pattern;
-  if (pread64(*page_idle_fd_, &bit_pattern, sizeof(bit_pattern), offset) !=
-      static_cast<ssize_t>(sizeof(bit_pattern))) {
-    PERFETTO_PLOG("Failed to read bit pattern at %" PRIi64 ".", offset);
-    return -1;
-  }
-  return static_cast<int>(bit_pattern & (1 << bit_offset));
-}
-
-uint64_t GetFirstPageShare(uint64_t addr, size_t size) {
-  // Our allocation is xxxx in this illustration:
-  //         +----------------------------------------------+
-  //         |             xxxxxxxxxx|xxxxxx                |
-  //         |             xxxxxxxxxx|xxxxxx                |
-  //         |             xxxxxxxxxx|xxxxxx                |
-  //         +-------------+---------------+----------------+
-  //         ^             ^         ^     ^
-  //         +             +         +     +
-  // page_aligned_addr  addr        end    addr + size
-  uint64_t page_aligned_addr = (addr / base::kPageSize) * base::kPageSize;
-  uint64_t end = page_aligned_addr + base::kPageSize;
-  if (end > addr + size) {
-    // The whole allocation is on the first page.
-    return size;
-  }
-
-  return base::kPageSize - (addr - page_aligned_addr);
-}
-
-uint64_t GetLastPageShare(uint64_t addr, size_t size) {
-  uint64_t last_page_size = (addr + size) % base::kPageSize;
-  if (last_page_size == 0) {
-    // Address ends at a page boundary, the whole last page is idle.
-    return base::kPageSize;
-  } else {
-    // Address does not end at a page boundary, only a subset of the last
-    // page should be attributed to this allocation.
-    return last_page_size;
-  }
-}
-
-}  // namespace profiling
-}  // namespace perfetto
diff --git a/src/profiling/memory/page_idle_checker.h b/src/profiling/memory/page_idle_checker.h
deleted file mode 100644
index f9c73cd..0000000
--- a/src/profiling/memory/page_idle_checker.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
-#define SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
-
-#include <set>
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include "perfetto/ext/base/scoped_file.h"
-
-namespace perfetto {
-namespace profiling {
-
-uint64_t GetFirstPageShare(uint64_t addr, size_t size);
-uint64_t GetLastPageShare(uint64_t addr, size_t size);
-
-class PageIdleChecker {
- public:
-  PageIdleChecker(base::ScopedFile page_idle_fd)
-      : page_idle_fd_(std::move(page_idle_fd)) {}
-
-  // Return number of bytes of allocation of size bytes starting at alloc that
-  // are on unreferenced pages.
-  // Return -1 on error.
-  int64_t OnIdlePage(uint64_t addr, size_t size);
-
-  void MarkPagesIdle();
-
- private:
-  void MarkPageIdle(uint64_t virt_page_nr);
-  // Return 1 if page is idle, 0 if it is not idle, or -1 on error.
-  int IsPageIdle(uint64_t virt_page_nr);
-
-  std::set<uint64_t> touched_virt_page_nrs_;
-
-  base::ScopedFile page_idle_fd_;
-};
-
-}  // namespace profiling
-}  // namespace perfetto
-
-#endif  // SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
diff --git a/src/profiling/memory/page_idle_checker_unittest.cc b/src/profiling/memory/page_idle_checker_unittest.cc
deleted file mode 100644
index ee751e9..0000000
--- a/src/profiling/memory/page_idle_checker_unittest.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/profiling/memory/page_idle_checker.h"
-
-#include "perfetto/ext/base/utils.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace profiling {
-namespace {
-
-TEST(PageIdleCheckerTest, AllOnFirstPageAligned) {
-  EXPECT_EQ(GetFirstPageShare(0, 10), 10u);
-}
-
-TEST(PageIdleCheckerTest, AllOnFirstPageUnAligned) {
-  EXPECT_EQ(GetFirstPageShare(10, 10), 10u);
-}
-
-TEST(PageIdleCheckerTest, WholeFirstPageAligned) {
-  EXPECT_EQ(GetFirstPageShare(0, base::kPageSize + 10), base::kPageSize);
-  EXPECT_EQ(GetLastPageShare(0, base::kPageSize + 10), 10u);
-}
-
-TEST(PageIdleCheckerTest, WholeLastPageAligned) {
-  EXPECT_EQ(GetFirstPageShare(0, 3 * base::kPageSize), base::kPageSize);
-  EXPECT_EQ(GetLastPageShare(0, 3 * base::kPageSize), base::kPageSize);
-}
-
-TEST(PageIdleCheckerTest, SomeFirstAndLast) {
-  EXPECT_EQ(GetFirstPageShare(10, 3 * base::kPageSize + 10),
-            base::kPageSize - 10);
-  EXPECT_EQ(GetLastPageShare(10, 3 * base::kPageSize + 10), 20u);
-}
-
-}  // namespace
-}  // namespace profiling
-}  // namespace perfetto
diff --git a/protos/perfetto/metrics/chrome/console_error_metric.proto b/src/profiling/memory/sampler.cc
similarity index 64%
copy from protos/perfetto/metrics/chrome/console_error_metric.proto
copy to src/profiling/memory/sampler.cc
index e22189e..9a05fc3 100644
--- a/protos/perfetto/metrics/chrome/console_error_metric.proto
+++ b/src/profiling/memory/sampler.cc
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto2";
+#include "src/profiling/memory/sampler.h"
 
-package perfetto.protos;
+namespace perfetto {
+namespace profiling {
 
-import "protos/perfetto/metrics/custom_options.proto";
-
-message ConsoleErrorMetric {
-  optional int64 all_errors = 1 [(unit) = "count_smallerIsBetter"];
-  optional int64 js_errors = 2 [(unit) = "count_smallerIsBetter"];
-  optional int64 network_errors = 3 [(unit) = "count_smallerIsBetter"];
+std::default_random_engine& GetGlobalRandomEngineLocked() {
+  static std::default_random_engine engine;
+  return engine;
 }
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/sampler.h b/src/profiling/memory/sampler.h
index 6740d17..d3d3fc0 100644
--- a/src/profiling/memory/sampler.h
+++ b/src/profiling/memory/sampler.h
@@ -29,6 +29,8 @@
 
 constexpr uint64_t kSamplerSeed = 1;
 
+std::default_random_engine& GetGlobalRandomEngineLocked();
+
 // Poisson sampler for memory allocations. We apply sampling individually to
 // each byte. The whole allocation gets accounted as often as the number of
 // sampled bytes it contains.
@@ -40,11 +42,11 @@
 // NB: not thread-safe, requires external synchronization.
 class Sampler {
  public:
-  Sampler(uint64_t sampling_interval)
-      : sampling_interval_(sampling_interval),
-        sampling_rate_(1.0 / static_cast<double>(sampling_interval)),
-        random_engine_(kSamplerSeed),
-        interval_to_next_sample_(NextSampleInterval()) {}
+  void SetSamplingInterval(uint64_t sampling_interval) {
+    sampling_interval_ = sampling_interval;
+    sampling_rate_ = 1.0 / static_cast<double>(sampling_interval_);
+    interval_to_next_sample_ = NextSampleInterval();
+  }
 
   // Returns number of bytes that should be be attributed to the sample.
   // If returned size is 0, the allocation should not be sampled.
@@ -60,7 +62,7 @@
  private:
   int64_t NextSampleInterval() {
     std::exponential_distribution<double> dist(sampling_rate_);
-    int64_t next = static_cast<int64_t>(dist(random_engine_));
+    int64_t next = static_cast<int64_t>(dist(GetGlobalRandomEngineLocked()));
     // The +1 corrects the distribution of the first value in the interval.
     // TODO(fmayer): Figure out why.
     return next + 1;
@@ -80,7 +82,6 @@
 
   uint64_t sampling_interval_;
   double sampling_rate_;
-  std::default_random_engine random_engine_;
   int64_t interval_to_next_sample_;
 };
 
diff --git a/src/profiling/memory/sampler_unittest.cc b/src/profiling/memory/sampler_unittest.cc
index db903ff..409fa4c 100644
--- a/src/profiling/memory/sampler_unittest.cc
+++ b/src/profiling/memory/sampler_unittest.cc
@@ -25,17 +25,23 @@
 namespace {
 
 TEST(SamplerTest, TestLarge) {
-  Sampler sampler(512);
+  GetGlobalRandomEngineLocked().seed(1);
+  Sampler sampler;
+  sampler.SetSamplingInterval(512);
   EXPECT_EQ(sampler.SampleSize(1024), 1024u);
 }
 
 TEST(SamplerTest, TestSmall) {
-  Sampler sampler(512);
+  GetGlobalRandomEngineLocked().seed(1);
+  Sampler sampler;
+  sampler.SetSamplingInterval(512);
   EXPECT_EQ(sampler.SampleSize(511), 512u);
 }
 
 TEST(SamplerTest, TestSequence) {
-  Sampler sampler(1);
+  GetGlobalRandomEngineLocked().seed(1);
+  Sampler sampler;
+  sampler.SetSamplingInterval(1);
   EXPECT_EQ(sampler.SampleSize(3), 3u);
   EXPECT_EQ(sampler.SampleSize(7), 7u);
   EXPECT_EQ(sampler.SampleSize(5), 5u);
diff --git a/src/profiling/memory/scoped_spinlock.cc b/src/profiling/memory/scoped_spinlock.cc
index c306d79..6e633a4 100644
--- a/src/profiling/memory/scoped_spinlock.cc
+++ b/src/profiling/memory/scoped_spinlock.cc
@@ -23,27 +23,39 @@
 #include "perfetto/ext/base/utils.h"
 
 namespace {
+constexpr bool IsPowerOfTwo(size_t v) {
+  return (v != 0 && ((v & (v - 1)) == 0));
+}
 // Wait for ~1s before timing out (+- spurious wakeups from the sleeps).
 constexpr unsigned kSleepAttempts = 1000;
-constexpr unsigned kLockAttemptsPerSleep = 1000;
+constexpr unsigned kLockAttemptsPerSleep = 1024;
 constexpr unsigned kSleepDurationUs = 1000;
+
+static_assert(IsPowerOfTwo(kLockAttemptsPerSleep),
+              "lock attempts of power of 2 produce faster code.");
 }  // namespace
 
 namespace perfetto {
 namespace profiling {
 
 void ScopedSpinlock::LockSlow(Mode mode) {
-  for (size_t attempt = 0; mode == Mode::Blocking ||
+  size_t sleeps = 0;
+  // We need to start with attempt = 1, otherwise
+  // attempt % kLockAttemptsPerSleep is zero for the first iteration.
+  for (size_t attempt = 1; mode == Mode::Blocking ||
                            attempt < kLockAttemptsPerSleep * kSleepAttempts;
        attempt++) {
     if (!lock_->load(std::memory_order_relaxed) &&
         PERFETTO_LIKELY(!lock_->exchange(true, std::memory_order_acquire))) {
       locked_ = true;
-      return;
+      break;
     }
-    if (attempt && attempt % kLockAttemptsPerSleep == 0)
+    if (attempt % kLockAttemptsPerSleep == 0) {
       usleep(kSleepDurationUs);
+      sleeps++;
+    }
   }
+  blocked_us_ = kSleepDurationUs * sleeps;
 }
 
 }  // namespace profiling
diff --git a/src/profiling/memory/scoped_spinlock.h b/src/profiling/memory/scoped_spinlock.h
index 67d8bd1..9d57d1c 100644
--- a/src/profiling/memory/scoped_spinlock.h
+++ b/src/profiling/memory/scoped_spinlock.h
@@ -71,10 +71,12 @@
   }
 
   bool locked() const { return locked_; }
+  size_t blocked_us() const { return blocked_us_; }
 
  private:
   void LockSlow(Mode mode);
   std::atomic<bool>* lock_;
+  size_t blocked_us_ = 0;
   bool locked_ = false;
 };
 
diff --git a/src/profiling/memory/shared_ring_buffer.cc b/src/profiling/memory/shared_ring_buffer.cc
index 511b1b1..b4c01f1 100644
--- a/src/profiling/memory/shared_ring_buffer.cc
+++ b/src/profiling/memory/shared_ring_buffer.cc
@@ -20,6 +20,7 @@
 #include <type_traits>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <inttypes.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
diff --git a/src/profiling/memory/shared_ring_buffer.h b/src/profiling/memory/shared_ring_buffer.h
index 4479e81..78575df 100644
--- a/src/profiling/memory/shared_ring_buffer.h
+++ b/src/profiling/memory/shared_ring_buffer.h
@@ -65,7 +65,7 @@
     Buffer(Buffer&&) = default;
     Buffer& operator=(Buffer&&) = default;
 
-    operator bool() const { return data != nullptr; }
+    explicit operator bool() const { return data != nullptr; }
 
     uint8_t* data = nullptr;
     size_t size = 0;
@@ -84,6 +84,7 @@
 
     // Fields below get set by GetStats as copies of atomics in MetadataPage.
     uint64_t failed_spinlocks;
+    uint64_t client_spinlock_blocked_us;
     bool hit_timeout;
   };
 
@@ -112,6 +113,8 @@
     stats.failed_spinlocks =
         meta_->failed_spinlocks.load(std::memory_order_relaxed);
     stats.hit_timeout = meta_->hit_timeout.load(std::memory_order_relaxed);
+    stats.client_spinlock_blocked_us =
+        meta_->client_spinlock_blocked_us.load(std::memory_order_relaxed);
     return stats;
   }
 
@@ -127,14 +130,41 @@
     return lock;
   }
 
+  void AddClientSpinlockBlockedUs(size_t n) {
+    meta_->client_spinlock_blocked_us.fetch_add(n, std::memory_order_relaxed);
+  }
+
+  uint64_t client_spinlock_blocked_us() {
+    return meta_->client_spinlock_blocked_us;
+  }
+
+  void SetShuttingDown() {
+    meta_->shutting_down.store(true, std::memory_order_relaxed);
+  }
+
+  bool shutting_down() {
+    return meta_->shutting_down.load(std::memory_order_relaxed);
+  }
+
+  void SetReaderPaused() {
+    meta_->reader_paused.store(true, std::memory_order_relaxed);
+  }
+
+  bool GetAndResetReaderPaused() {
+    return meta_->reader_paused.exchange(false, std::memory_order_relaxed);
+  }
+
   // Exposed for fuzzers.
   struct MetadataPage {
     alignas(uint64_t) std::atomic<bool> spinlock;
     std::atomic<uint64_t> read_pos;
     std::atomic<uint64_t> write_pos;
 
+    std::atomic<uint64_t> client_spinlock_blocked_us;
     std::atomic<uint64_t> failed_spinlocks;
     alignas(uint64_t) std::atomic<bool> hit_timeout;
+    alignas(uint64_t) std::atomic<bool> shutting_down;
+    alignas(uint64_t) std::atomic<bool> reader_paused;
     // For stats that are only accessed by a single thread or under the
     // spinlock, members of this struct are directly modified. Other stats use
     // the atomics above this struct.
diff --git a/src/profiling/memory/shared_ring_buffer_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
index 0fd03ce..6eb47e2 100644
--- a/src/profiling/memory/shared_ring_buffer_fuzzer.cc
+++ b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
@@ -16,6 +16,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <unistd.h>
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
diff --git a/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
index be5bbb9..08f5812 100644
--- a/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
+++ b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
@@ -16,6 +16,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <unistd.h>
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
diff --git a/src/profiling/memory/system_property.h b/src/profiling/memory/system_property.h
index 38aa35e..f713940 100644
--- a/src/profiling/memory/system_property.h
+++ b/src/profiling/memory/system_property.h
@@ -50,7 +50,7 @@
 
     friend class SystemProperties;
     ~Handle();
-    operator bool();
+    explicit operator bool();
 
    private:
     explicit Handle(SystemProperties* system_properties, std::string property);
diff --git a/src/profiling/memory/unhooked_allocator.h b/src/profiling/memory/unhooked_allocator.h
index 45a6262..b379e36 100644
--- a/src/profiling/memory/unhooked_allocator.h
+++ b/src/profiling/memory/unhooked_allocator.h
@@ -49,7 +49,8 @@
       : unhooked_malloc_(unhooked_malloc), unhooked_free_(unhooked_free) {}
 
   template <typename U>
-  constexpr UnhookedAllocator(const UnhookedAllocator<U>& other) noexcept
+  constexpr explicit UnhookedAllocator(
+      const UnhookedAllocator<U>& other) noexcept
       : unhooked_malloc_(other.unhooked_malloc_),
         unhooked_free_(other.unhooked_free_) {}
 
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 397884d..868eb06 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -51,6 +51,7 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/thread_task_runner.h"
 
+#include "src/profiling/memory/unwound_messages.h"
 #include "src/profiling/memory/wire_protocol.h"
 
 namespace perfetto {
@@ -58,14 +59,16 @@
 namespace {
 
 constexpr base::TimeMillis kMapsReparseInterval{500};
+constexpr uint32_t kRetryDelayMs = 100;
 
-constexpr size_t kMaxFrames = 1000;
+constexpr size_t kMaxFrames = 500;
 
 // We assume average ~300us per unwind. If we handle up to 1000 unwinds, this
 // makes sure other tasks get to be run at least every 300ms if the unwinding
 // saturates this thread.
 constexpr size_t kUnwindBatchSize = 1000;
 constexpr size_t kRecordBatchSize = 1024;
+constexpr size_t kMaxAllocRecordArenaSize = 2 * kRecordBatchSize;
 
 #pragma GCC diagnostic push
 // We do not care about deterministic destructor order.
@@ -128,7 +131,10 @@
     frame_data.function_name = "ERROR READING REGISTERS";
     frame_data.map_name = "ERROR";
 
-    out->frames.emplace_back(frame_data, "");
+    out->frames.clear();
+    out->build_ids.clear();
+    out->frames.emplace_back(std::move(frame_data));
+    out->build_ids.emplace_back("");
     out->error = true;
     return false;
   }
@@ -141,8 +147,8 @@
   unwindstack::Unwinder unwinder(kMaxFrames, &metadata->fd_maps, regs.get(),
                                  mems);
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
-  unwinder.SetJitDebug(metadata->jit_debug.get(), regs->Arch());
-  unwinder.SetDexFiles(metadata->dex_files.get(), regs->Arch());
+  unwinder.SetJitDebug(metadata->jit_debug.get());
+  unwinder.SetDexFiles(metadata->dex_files.get());
 #endif
   // Suppress incorrect "variable may be uninitialized" error for if condition
   // after this loop. error_code = LastErrorCode gets run at least once.
@@ -162,18 +168,22 @@
       ReadFromRawData(regs.get(), alloc_metadata->register_data);
       out->reparsed_map = true;
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
-      unwinder.SetJitDebug(metadata->jit_debug.get(), regs->Arch());
-      unwinder.SetDexFiles(metadata->dex_files.get(), regs->Arch());
+      unwinder.SetJitDebug(metadata->jit_debug.get());
+      unwinder.SetDexFiles(metadata->dex_files.get());
 #endif
     }
+    out->frames.swap(unwinder.frames());  // Provide the unwinder buffer to use.
     unwinder.Unwind(&kSkipMaps, /*map_suffixes_to_ignore=*/nullptr);
+    out->frames.swap(unwinder.frames());  // Take the buffer back.
     error_code = unwinder.LastErrorCode();
-    if (error_code != unwindstack::ERROR_INVALID_MAP)
+    if (error_code != unwindstack::ERROR_INVALID_MAP &&
+        (unwinder.warnings() & unwindstack::WARNING_DEX_PC_NOT_IN_MAP) == 0) {
       break;
+    }
   }
-  std::vector<unwindstack::FrameData> frames = unwinder.ConsumeFrames();
-  for (unwindstack::FrameData& fd : frames) {
-    out->frames.emplace_back(metadata->AnnotateFrame(std::move(fd)));
+  out->build_ids.resize(out->frames.size());
+  for (size_t i = 0; i < out->frames.size(); ++i) {
+    out->build_ids[i] = metadata->GetBuildId(out->frames[i]);
   }
 
   if (error_code != unwindstack::ERROR_NONE) {
@@ -183,7 +193,8 @@
         "ERROR " + StringifyLibUnwindstackError(error_code);
     frame_data.map_name = "ERROR";
 
-    out->frames.emplace_back(std::move(frame_data), "");
+    out->frames.emplace_back(std::move(frame_data));
+    out->build_ids.emplace_back("");
     out->error = true;
   }
   return true;
@@ -197,16 +208,12 @@
     return;
   }
 
-  HandleUnwindBatch(peer_pid);
   ClientData& client_data = it->second;
+  ReadAndUnwindBatch(&client_data);
   SharedRingBuffer& shmem = client_data.shmem;
 
-  if (!client_data.alloc_records.empty()) {
-    delegate_->PostAllocRecord(std::move(client_data.alloc_records));
-    client_data.alloc_records.clear();
-  }
   if (!client_data.free_records.empty()) {
-    delegate_->PostFreeRecord(std::move(client_data.free_records));
+    delegate_->PostFreeRecord(this, std::move(client_data.free_records));
     client_data.free_records.clear();
   }
 
@@ -221,19 +228,55 @@
   DataSourceInstanceID ds_id = client_data.data_source_instance_id;
 
   client_data_.erase(it);
+  if (client_data_.empty()) {
+    // We got rid of the last client. Flush and destruct AllocRecords in
+    // arena. Disable the arena (will not accept returning borrowed records)
+    // in case there are pending AllocRecords on the main thread.
+    alloc_record_arena_.Disable();
+  }
   // The erase invalidates the self pointer.
   self = nullptr;
-  delegate_->PostSocketDisconnected(ds_id, peer_pid, stats);
+  delegate_->PostSocketDisconnected(this, ds_id, peer_pid, stats);
 }
 
 void UnwindingWorker::OnDataAvailable(base::UnixSocket* self) {
   // Drain buffer to clear the notification.
   char recv_buf[kUnwindBatchSize];
   self->Receive(recv_buf, sizeof(recv_buf));
-  HandleUnwindBatch(self->peer_pid());
+  BatchUnwindJob(self->peer_pid());
 }
 
-void UnwindingWorker::HandleUnwindBatch(pid_t peer_pid) {
+UnwindingWorker::ReadAndUnwindBatchResult UnwindingWorker::ReadAndUnwindBatch(
+    ClientData* client_data) {
+  SharedRingBuffer& shmem = client_data->shmem;
+  SharedRingBuffer::Buffer buf;
+
+  size_t i;
+  for (i = 0; i < kUnwindBatchSize; ++i) {
+    uint64_t reparses_before = client_data->metadata.reparses;
+    buf = shmem.BeginRead();
+    if (!buf)
+      break;
+    HandleBuffer(this, &alloc_record_arena_, buf, client_data,
+                 client_data->sock->peer_pid(), delegate_);
+    shmem.EndRead(std::move(buf));
+    // Reparsing takes time, so process the rest in a new batch to avoid timing
+    // out.
+    if (reparses_before < client_data->metadata.reparses) {
+      return ReadAndUnwindBatchResult::kHasMore;
+    }
+  }
+
+  if (i == kUnwindBatchSize) {
+    return ReadAndUnwindBatchResult::kHasMore;
+  } else if (i > 0) {
+    return ReadAndUnwindBatchResult::kReadSome;
+  } else {
+    return ReadAndUnwindBatchResult::kReadNone;
+  }
+}
+
+void UnwindingWorker::BatchUnwindJob(pid_t peer_pid) {
   auto it = client_data_.find(peer_pid);
   if (it == client_data_.end()) {
     // This can happen if the client disconnected before the buffer was fully
@@ -242,39 +285,38 @@
     return;
   }
 
+  bool job_reposted = false;
+  bool reader_paused = false;
   ClientData& client_data = it->second;
-  SharedRingBuffer& shmem = client_data.shmem;
-  SharedRingBuffer::Buffer buf;
-
-  size_t i;
-  bool repost_task = false;
-  for (i = 0; i < kUnwindBatchSize; ++i) {
-    uint64_t reparses_before = client_data.metadata.reparses;
-    buf = shmem.BeginRead();
-    if (!buf)
+  switch (ReadAndUnwindBatch(&client_data)) {
+    case ReadAndUnwindBatchResult::kHasMore:
+      thread_task_runner_.get()->PostTask(
+          [this, peer_pid] { BatchUnwindJob(peer_pid); });
+      job_reposted = true;
       break;
-    HandleBuffer(buf, &client_data, client_data.sock->peer_pid(), delegate_);
-    shmem.EndRead(std::move(buf));
-    // Reparsing takes time, so process the rest in a new batch to avoid timing
-    // out.
-    if (reparses_before < client_data.metadata.reparses) {
-      repost_task = true;
+    case ReadAndUnwindBatchResult::kReadSome:
+      thread_task_runner_.get()->PostDelayedTask(
+          [this, peer_pid] { BatchUnwindJob(peer_pid); }, kRetryDelayMs);
+      job_reposted = true;
       break;
-    }
+    case ReadAndUnwindBatchResult::kReadNone:
+      client_data.shmem.SetReaderPaused();
+      reader_paused = true;
+      break;
   }
 
-  // Always repost if we have gone through the whole batch.
-  if (i == kUnwindBatchSize)
-    repost_task = true;
-
-  if (repost_task) {
-    thread_task_runner_.get()->PostTask(
-        [this, peer_pid] { HandleUnwindBatch(peer_pid); });
-  }
+  // We need to either repost the job, or set the reader paused bit. By
+  // setting that bit, we inform the client that we want to be notified when
+  // new data is written to the shared memory buffer.
+  // If we do neither of these things, we will not read from the shared memory
+  // buffer again.
+  PERFETTO_CHECK(job_reposted || reader_paused);
 }
 
 // static
-void UnwindingWorker::HandleBuffer(const SharedRingBuffer::Buffer& buf,
+void UnwindingWorker::HandleBuffer(UnwindingWorker* self,
+                                   AllocRecordArena* alloc_record_arena,
+                                   const SharedRingBuffer::Buffer& buf,
                                    ClientData* client_data,
                                    pid_t peer_pid,
                                    Delegate* delegate) {
@@ -291,20 +333,16 @@
   }
 
   if (msg.record_type == RecordType::Malloc) {
-    AllocRecord rec;
-    rec.alloc_metadata = *msg.alloc_header;
-    rec.pid = peer_pid;
-    rec.data_source_instance_id = data_source_instance_id;
+    std::unique_ptr<AllocRecord> rec = alloc_record_arena->BorrowAllocRecord();
+    rec->alloc_metadata = *msg.alloc_header;
+    rec->pid = peer_pid;
+    rec->data_source_instance_id = data_source_instance_id;
     auto start_time_us = base::GetWallTimeNs() / 1000;
-    DoUnwind(&msg, unwinding_metadata, &rec);
-    rec.unwinding_time_us = static_cast<uint64_t>(
+    if (!client_data->stream_allocations)
+      DoUnwind(&msg, unwinding_metadata, rec.get());
+    rec->unwinding_time_us = static_cast<uint64_t>(
         ((base::GetWallTimeNs() / 1000) - start_time_us).count());
-    client_data->alloc_records.emplace_back(std::move(rec));
-    if (client_data->alloc_records.size() == kRecordBatchSize) {
-      delegate->PostAllocRecord(std::move(client_data->alloc_records));
-      client_data->alloc_records.clear();
-      client_data->alloc_records.reserve(kRecordBatchSize);
-    }
+    delegate->PostAllocRecord(self, std::move(rec));
   } else if (msg.record_type == RecordType::Free) {
     FreeRecord rec;
     rec.pid = peer_pid;
@@ -313,10 +351,17 @@
     memcpy(&rec.entry, msg.free_header, sizeof(*msg.free_header));
     client_data->free_records.emplace_back(std::move(rec));
     if (client_data->free_records.size() == kRecordBatchSize) {
-      delegate->PostFreeRecord(std::move(client_data->free_records));
+      delegate->PostFreeRecord(self, std::move(client_data->free_records));
       client_data->free_records.clear();
       client_data->free_records.reserve(kRecordBatchSize);
     }
+  } else if (msg.record_type == RecordType::HeapName) {
+    HeapNameRecord rec;
+    rec.pid = peer_pid;
+    rec.data_source_instance_id = data_source_instance_id;
+    memcpy(&rec.entry, msg.heap_name_header, sizeof(*msg.heap_name_header));
+    rec.entry.heap_name[sizeof(rec.entry.heap_name) - 1] = '\0';
+    delegate->PostHeapNameRecord(self, std::move(rec));
   } else {
     PERFETTO_DFATAL_OR_ELOG("Invalid record type.");
   }
@@ -349,12 +394,13 @@
       std::move(metadata),
       std::move(handoff_data.shmem),
       std::move(handoff_data.client_config),
-      {},
+      handoff_data.stream_allocations,
       {},
   };
   client_data.free_records.reserve(kRecordBatchSize);
-  client_data.alloc_records.reserve(kRecordBatchSize);
+  client_data.shmem.SetReaderPaused();
   client_data_.emplace(peer_pid, std::move(client_data));
+  alloc_record_arena_.Enable();
 }
 
 void UnwindingWorker::PostDisconnectSocket(pid_t pid) {
@@ -372,9 +418,37 @@
   }
   ClientData& client_data = it->second;
   // Shutdown and call OnDisconnect handler.
+  client_data.shmem.SetShuttingDown();
   client_data.sock->Shutdown(/* notify= */ true);
 }
 
+std::unique_ptr<AllocRecord> AllocRecordArena::BorrowAllocRecord() {
+  std::lock_guard<std::mutex> l(*alloc_records_mutex_);
+  if (!alloc_records_.empty()) {
+    std::unique_ptr<AllocRecord> result = std::move(alloc_records_.back());
+    alloc_records_.pop_back();
+    return result;
+  }
+  return std::unique_ptr<AllocRecord>(new AllocRecord());
+}
+
+void AllocRecordArena::ReturnAllocRecord(std::unique_ptr<AllocRecord> record) {
+  std::lock_guard<std::mutex> l(*alloc_records_mutex_);
+  if (enabled_ && record && alloc_records_.size() < kMaxAllocRecordArenaSize)
+    alloc_records_.emplace_back(std::move(record));
+}
+
+void AllocRecordArena::Disable() {
+  std::lock_guard<std::mutex> l(*alloc_records_mutex_);
+  alloc_records_.clear();
+  enabled_ = false;
+}
+
+void AllocRecordArena::Enable() {
+  std::lock_guard<std::mutex> l(*alloc_records_mutex_);
+  enabled_ = true;
+}
+
 UnwindingWorker::Delegate::~Delegate() = default;
 
 }  // namespace profiling
diff --git a/src/profiling/memory/unwinding.h b/src/profiling/memory/unwinding.h
index 880d01b..6b5bcdb 100644
--- a/src/profiling/memory/unwinding.h
+++ b/src/profiling/memory/unwinding.h
@@ -37,13 +37,35 @@
 
 bool DoUnwind(WireMessage*, UnwindingMetadata* metadata, AllocRecord* out);
 
+// AllocRecords are expensive to construct and destruct. We have seen up to
+// 10 % of total CPU of heapprofd being used to destruct them. That is why
+// we re-use them to cut CPU usage significantly.
+class AllocRecordArena {
+ public:
+  AllocRecordArena() : alloc_records_mutex_(new std::mutex()) {}
+
+  void ReturnAllocRecord(std::unique_ptr<AllocRecord>);
+  std::unique_ptr<AllocRecord> BorrowAllocRecord();
+
+  void Enable();
+  void Disable();
+
+ private:
+  std::unique_ptr<std::mutex> alloc_records_mutex_;
+  std::vector<std::unique_ptr<AllocRecord>> alloc_records_;
+  bool enabled_ = true;
+};
+
 class UnwindingWorker : public base::UnixSocket::EventListener {
  public:
   class Delegate {
    public:
-    virtual void PostAllocRecord(std::vector<AllocRecord>) = 0;
-    virtual void PostFreeRecord(std::vector<FreeRecord>) = 0;
-    virtual void PostSocketDisconnected(DataSourceInstanceID,
+    virtual void PostAllocRecord(UnwindingWorker*,
+                                 std::unique_ptr<AllocRecord>) = 0;
+    virtual void PostFreeRecord(UnwindingWorker*, std::vector<FreeRecord>) = 0;
+    virtual void PostHeapNameRecord(UnwindingWorker*, HeapNameRecord rec) = 0;
+    virtual void PostSocketDisconnected(UnwindingWorker*,
+                                        DataSourceInstanceID,
                                         pid_t pid,
                                         SharedRingBuffer::Stats stats) = 0;
     virtual ~Delegate();
@@ -56,6 +78,7 @@
     base::ScopedFile mem_fd;
     SharedRingBuffer shmem;
     ClientConfiguration client_config;
+    bool stream_allocations;
   };
 
   UnwindingWorker(Delegate* delegate, base::ThreadTaskRunner thread_task_runner)
@@ -65,6 +88,9 @@
   // Public API safe to call from other threads.
   void PostDisconnectSocket(pid_t pid);
   void PostHandoffSocket(HandoffData);
+  void ReturnAllocRecord(std::unique_ptr<AllocRecord> record) {
+    alloc_record_arena_.ReturnAllocRecord(std::move(record));
+  }
 
   // Implementation of UnixSocket::EventListener.
   // Do not call explicitly.
@@ -83,12 +109,14 @@
     UnwindingMetadata metadata;
     SharedRingBuffer shmem;
     ClientConfiguration client_config;
+    bool stream_allocations;
     std::vector<FreeRecord> free_records;
-    std::vector<AllocRecord> alloc_records;
   };
 
-  // static and public for testing/fuzzing
-  static void HandleBuffer(const SharedRingBuffer::Buffer& buf,
+  // public for testing/fuzzing
+  static void HandleBuffer(UnwindingWorker* self,
+                           AllocRecordArena* alloc_record_arena,
+                           const SharedRingBuffer::Buffer& buf,
                            ClientData* client_data,
                            pid_t peer_pid,
                            Delegate* delegate);
@@ -96,9 +124,17 @@
  private:
   void HandleHandoffSocket(HandoffData data);
   void HandleDisconnectSocket(pid_t pid);
+  std::unique_ptr<AllocRecord> BorrowAllocRecord();
 
-  void HandleUnwindBatch(pid_t);
+  enum class ReadAndUnwindBatchResult {
+    kHasMore,
+    kReadSome,
+    kReadNone,
+  };
+  ReadAndUnwindBatchResult ReadAndUnwindBatch(ClientData* client_data);
+  void BatchUnwindJob(pid_t);
 
+  AllocRecordArena alloc_record_arena_;
   std::map<pid_t, ClientData> client_data_;
   Delegate* delegate_;
 
diff --git a/src/profiling/memory/unwinding_fuzzer.cc b/src/profiling/memory/unwinding_fuzzer.cc
index 7328c94..e24ce4f 100644
--- a/src/profiling/memory/unwinding_fuzzer.cc
+++ b/src/profiling/memory/unwinding_fuzzer.cc
@@ -17,6 +17,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "src/profiling/common/unwind_support.h"
@@ -29,9 +30,12 @@
 namespace {
 
 class NopDelegate : public UnwindingWorker::Delegate {
-  void PostAllocRecord(std::vector<AllocRecord>) override {}
-  void PostFreeRecord(std::vector<FreeRecord>) override {}
-  void PostSocketDisconnected(DataSourceInstanceID,
+  void PostAllocRecord(UnwindingWorker*,
+                       std::unique_ptr<AllocRecord>) override {}
+  void PostFreeRecord(UnwindingWorker*, std::vector<FreeRecord>) override {}
+  void PostHeapNameRecord(UnwindingWorker*, HeapNameRecord) override {}
+  void PostSocketDisconnected(UnwindingWorker*,
+                              DataSourceInstanceID,
                               pid_t,
                               SharedRingBuffer::Stats) override {}
 };
@@ -48,7 +52,10 @@
   UnwindingWorker::ClientData client_data{
       id, {}, std::move(metadata), {}, {}, {}, {},
   };
-  UnwindingWorker::HandleBuffer(buf, &client_data, self_pid, &nop_delegate);
+
+  AllocRecordArena arena;
+  UnwindingWorker::HandleBuffer(nullptr, &arena, buf, &client_data, self_pid,
+                                &nop_delegate);
   return 0;
 }
 
diff --git a/src/profiling/memory/unwinding_unittest.cc b/src/profiling/memory/unwinding_unittest.cc
index 321e7fe..1684310 100644
--- a/src/profiling/memory/unwinding_unittest.cc
+++ b/src/profiling/memory/unwinding_unittest.cc
@@ -22,6 +22,7 @@
 #include <sys/types.h>
 #include <unwindstack/RegsGetLocal.h>
 
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "src/profiling/common/unwind_support.h"
 #include "src/profiling/memory/client.h"
@@ -105,27 +106,27 @@
   std::unique_ptr<AllocMetadata> metadata(new AllocMetadata);
   *metadata = {};
 
-  const char* stackbase = GetThreadStackBase();
-  const char* stacktop = reinterpret_cast<char*>(__builtin_frame_address(0));
+  const char* stackend = GetThreadStackRange().end;
+  const char* stackptr = reinterpret_cast<char*>(__builtin_frame_address(0));
   // Need to zero-initialize to make MSAN happy. MSAN does not see the writes
   // from AsmGetRegs (as it is in assembly) and complains otherwise.
   memset(metadata->register_data, 0, sizeof(metadata->register_data));
   unwindstack::AsmGetRegs(metadata->register_data);
 
-  if (stackbase < stacktop) {
-    PERFETTO_FATAL("Stacktop >= stackbase.");
+  if (stackend < stackptr) {
+    PERFETTO_FATAL("Stacktop >= stackend.");
     return {nullptr, nullptr};
   }
-  size_t stack_size = static_cast<size_t>(stackbase - stacktop);
+  size_t stack_size = static_cast<size_t>(stackend - stackptr);
 
   metadata->alloc_size = 10;
   metadata->alloc_address = 0x10;
-  metadata->stack_pointer = reinterpret_cast<uint64_t>(stacktop);
+  metadata->stack_pointer = reinterpret_cast<uint64_t>(stackptr);
   metadata->arch = unwindstack::Regs::CurrentArch();
   metadata->sequence_number = 1;
 
   std::unique_ptr<uint8_t[]> payload(new uint8_t[stack_size]);
-  UnsafeMemcpy(&payload[0], stacktop, stack_size);
+  UnsafeMemcpy(&payload[0], stackptr, stack_size);
 
   *msg = {};
   msg->alloc_header = metadata.get();
@@ -146,7 +147,7 @@
   ASSERT_GT(out.frames.size(), 0u);
   int st;
   std::unique_ptr<char, base::FreeDeleter> demangled(abi::__cxa_demangle(
-      out.frames[0].frame.function_name.c_str(), nullptr, nullptr, &st));
+      out.frames[0].function_name.c_str(), nullptr, nullptr, &st));
   ASSERT_EQ(st, 0) << "mangled: " << demangled.get()
                    << ", frames: " << out.frames.size();
   ASSERT_STREQ(demangled.get(),
@@ -168,7 +169,7 @@
   ASSERT_GT(out.frames.size(), 0u);
   int st;
   std::unique_ptr<char, base::FreeDeleter> demangled(abi::__cxa_demangle(
-      out.frames[0].frame.function_name.c_str(), nullptr, nullptr, &st));
+      out.frames[0].function_name.c_str(), nullptr, nullptr, &st));
   ASSERT_EQ(st, 0) << "mangled: " << demangled.get()
                    << ", frames: " << out.frames.size();
   ASSERT_STREQ(demangled.get(),
@@ -176,6 +177,13 @@
                "namespace)::GetRecord(perfetto::profiling::WireMessage*)");
 }
 
+TEST(AllocRecordArenaTest, Smoke) {
+  AllocRecordArena a;
+  auto borrowed = a.BorrowAllocRecord();
+  EXPECT_NE(borrowed, nullptr);
+  a.ReturnAllocRecord(std::move(borrowed));
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/unwound_messages.h b/src/profiling/memory/unwound_messages.h
index b660056..cf0401d 100644
--- a/src/profiling/memory/unwound_messages.h
+++ b/src/profiling/memory/unwound_messages.h
@@ -35,7 +35,8 @@
   uint64_t data_source_instance_id;
   uint64_t timestamp;
   AllocMetadata alloc_metadata;
-  std::vector<FrameData> frames;
+  std::vector<unwindstack::FrameData> frames;
+  std::vector<std::string> build_ids;
 };
 
 // Batch of deallocations.
@@ -46,6 +47,12 @@
   FreeEntry entry;
 };
 
+struct HeapNameRecord {
+  pid_t pid;
+  uint64_t data_source_instance_id;
+  HeapName entry;
+};
+
 }  // namespace profiling
 }  // namespace perfetto
 
diff --git a/src/profiling/memory/wire_protocol.cc b/src/profiling/memory/wire_protocol.cc
index 5c67660..29ea042 100644
--- a/src/profiling/memory/wire_protocol.cc
+++ b/src/profiling/memory/wire_protocol.cc
@@ -19,14 +19,25 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/utils.h"
+#include "src/profiling/memory/shared_ring_buffer.h"
 
 #include <sys/socket.h>
 #include <sys/types.h>
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+#include <bionic/mte.h>
+#else
+struct ScopedDisableMTE {
+  // Silence unused variable warnings in non-Android builds.
+  ScopedDisableMTE() {}
+};
+#endif
+
 namespace perfetto {
 namespace profiling {
 
 namespace {
+
 template <typename T>
 bool ViewAndAdvance(char** ptr, T** out, const char* end) {
   if (end - sizeof(T) < *ptr)
@@ -39,76 +50,75 @@
 // We need this to prevent crashes due to FORTIFY_SOURCE.
 void UnsafeMemcpy(char* dest, const char* src, size_t n)
     __attribute__((no_sanitize("address", "hwaddress"))) {
+  ScopedDisableMTE m;
   for (size_t i = 0; i < n; ++i) {
     dest[i] = src[i];
   }
 }
+
+template <typename F>
+int64_t WithBuffer(SharedRingBuffer* shmem, size_t total_size, F fn) {
+  SharedRingBuffer::Buffer buf;
+  {
+    ScopedSpinlock lock = shmem->AcquireLock(ScopedSpinlock::Mode::Try);
+    if (!lock.locked()) {
+      PERFETTO_DLOG("Failed to acquire spinlock.");
+      errno = EAGAIN;
+      return -1;
+    }
+    buf = shmem->BeginWrite(lock, total_size);
+  }
+  if (!buf) {
+    PERFETTO_DLOG("Buffer overflow.");
+    shmem->EndWrite(std::move(buf));
+    errno = EAGAIN;
+    return -1;
+  }
+
+  fn(&buf);
+
+  auto bytes_free = buf.bytes_free;
+  shmem->EndWrite(std::move(buf));
+  return static_cast<int64_t>(bytes_free);
+}
+
 }  // namespace
 
 int64_t SendWireMessage(SharedRingBuffer* shmem, const WireMessage& msg) {
   switch (msg.record_type) {
     case RecordType::Malloc: {
-      PERFETTO_DCHECK(msg.free_header == nullptr);
-      PERFETTO_DCHECK(msg.alloc_header != nullptr);
       size_t total_size = sizeof(msg.record_type) + sizeof(*msg.alloc_header) +
                           msg.payload_size;
-      SharedRingBuffer::Buffer buf;
-      {
-        ScopedSpinlock lock = shmem->AcquireLock(ScopedSpinlock::Mode::Try);
-        if (!lock.locked()) {
-          PERFETTO_DLOG("Failed to acquire spinlock.");
-          errno = EAGAIN;
-          return -1;
-        }
-        buf = shmem->BeginWrite(lock, total_size);
-      }
-      if (!buf) {
-        PERFETTO_DLOG("Buffer overflow.");
-        shmem->EndWrite(std::move(buf));
-        errno = EAGAIN;
-        return -1;
-      }
-
-      memcpy(buf.data, &msg.record_type, sizeof(msg.record_type));
-      memcpy(buf.data + sizeof(msg.record_type), msg.alloc_header,
-             sizeof(*msg.alloc_header));
-      UnsafeMemcpy(reinterpret_cast<char*>(buf.data) + sizeof(msg.record_type) +
-                       sizeof(*msg.alloc_header),
-                   msg.payload, msg.payload_size);
-      auto bytes_free = buf.bytes_free;
-      shmem->EndWrite(std::move(buf));
-      return static_cast<int64_t>(bytes_free);
+      return WithBuffer(
+          shmem, total_size, [msg](SharedRingBuffer::Buffer* buf) {
+            memcpy(buf->data, &msg.record_type, sizeof(msg.record_type));
+            memcpy(buf->data + sizeof(msg.record_type), msg.alloc_header,
+                   sizeof(*msg.alloc_header));
+            UnsafeMemcpy(reinterpret_cast<char*>(buf->data) +
+                             sizeof(msg.record_type) +
+                             sizeof(*msg.alloc_header),
+                         msg.payload, msg.payload_size);
+          });
     }
     case RecordType::Free: {
-      PERFETTO_DCHECK(msg.free_header != nullptr);
-      PERFETTO_DCHECK(msg.alloc_header == nullptr);
-      PERFETTO_DCHECK(msg.payload == nullptr);
-      PERFETTO_DCHECK(msg.payload_size == 0);
       constexpr size_t total_size =
           sizeof(msg.record_type) + sizeof(*msg.free_header);
-      SharedRingBuffer::Buffer buf;
-      {
-        ScopedSpinlock lock = shmem->AcquireLock(ScopedSpinlock::Mode::Try);
-        if (!lock.locked()) {
-          PERFETTO_DLOG("Failed to acquire spinlock.");
-          errno = EAGAIN;
-          return -1;
-        }
-        buf = shmem->BeginWrite(lock, total_size);
-      }
-      if (!buf) {
-        PERFETTO_DLOG("Buffer overflow.");
-        shmem->EndWrite(std::move(buf));
-        errno = EAGAIN;
-        return -1;
-      }
-
-      memcpy(buf.data, &msg.record_type, sizeof(msg.record_type));
-      memcpy(buf.data + sizeof(msg.record_type), msg.free_header,
-             sizeof(*msg.free_header));
-      auto bytes_free = buf.bytes_free;
-      shmem->EndWrite(std::move(buf));
-      return static_cast<int64_t>(bytes_free);
+      return WithBuffer(
+          shmem, total_size, [msg](SharedRingBuffer::Buffer* buf) {
+            memcpy(buf->data, &msg.record_type, sizeof(msg.record_type));
+            memcpy(buf->data + sizeof(msg.record_type), msg.free_header,
+                   sizeof(*msg.free_header));
+          });
+    }
+    case RecordType::HeapName: {
+      constexpr size_t total_size =
+          sizeof(msg.record_type) + sizeof(*msg.heap_name_header);
+      return WithBuffer(
+          shmem, total_size, [msg](SharedRingBuffer::Buffer* buf) {
+            memcpy(buf->data, &msg.record_type, sizeof(msg.record_type));
+            memcpy(buf->data + sizeof(msg.record_type), msg.heap_name_header,
+                   sizeof(*msg.heap_name_header));
+          });
     }
   }
 }
@@ -141,6 +151,11 @@
       PERFETTO_DFATAL_OR_ELOG("Cannot read free header.");
       return false;
     }
+  } else if (*record_type == RecordType::HeapName) {
+    if (!ViewAndAdvance<HeapName>(&buf, &out->heap_name_header, end)) {
+      PERFETTO_DFATAL_OR_ELOG("Cannot read free header.");
+      return false;
+    }
   } else {
     PERFETTO_DFATAL_OR_ELOG("Invalid record type.");
     return false;
@@ -148,5 +163,21 @@
   return true;
 }
 
+uint64_t GetHeapSamplingInterval(const ClientConfiguration& cli_config,
+                                 const char* heap_name) {
+  for (uint32_t i = 0; i < cli_config.num_heaps; ++i) {
+    const ClientConfigurationHeap& heap = cli_config.heaps[i];
+    static_assert(sizeof(heap.name) == HEAPPROFD_HEAP_NAME_SZ,
+                  "correct heap name size");
+    if (strncmp(&heap.name[0], heap_name, HEAPPROFD_HEAP_NAME_SZ) == 0) {
+      return heap.interval;
+    }
+  }
+  if (cli_config.all_heaps) {
+    return cli_config.default_interval;
+  }
+  return 0;
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/wire_protocol.h b/src/profiling/memory/wire_protocol.h
index 128be2d..a018fba 100644
--- a/src/profiling/memory/wire_protocol.h
+++ b/src/profiling/memory/wire_protocol.h
@@ -29,9 +29,16 @@
 #include <unwindstack/MachineX86.h>
 #include <unwindstack/MachineX86_64.h>
 
-#include "perfetto/profiling/memory/client_ext.h"
+#include "perfetto/profiling/memory/heap_profile.h"
 #include "src/profiling/memory/shared_ring_buffer.h"
 
+// Make sure the alignment is the same on 32 and 64 bit architectures. This
+// is to ensure the structs below are laid out in exactly the same way for
+// both of those, at the same build.
+// The maximum alignment of every type T is sizeof(T), so we overalign that.
+// E.g., the alignment for uint64_t is 4 bytes on 32, and 8 bytes on 64 bit.
+#define PERFETTO_CROSS_ABI_ALIGNED(type) alignas(sizeof(type)) type
+
 namespace perfetto {
 
 namespace base {
@@ -40,30 +47,6 @@
 
 namespace profiling {
 
-struct ClientConfiguration {
-  // On average, sample one allocation every interval bytes,
-  // If interval == 1, sample every allocation.
-  // Must be >= 1.
-  uint64_t interval;
-  bool block_client;
-  uint64_t block_client_timeout_us;
-  bool disable_fork_teardown;
-  bool disable_vfork_detection;
-  char heaps[64][HEAPPROFD_HEAP_NAME_SZ];
-  uint64_t num_heaps;
-  // Just double check that the array sizes are in correct order.
-  static_assert(sizeof(heaps[0]) == HEAPPROFD_HEAP_NAME_SZ, "");
-};
-
-// Types needed for the wire format used for communication between the client
-// and heapprofd. The basic format of a record is
-// record size (uint64_t) | record type (RecordType = uint64_t) | record
-// If record type is malloc, the record format is AllocMetdata | raw stack.
-// If the record type is free, the record is a sequence of FreeBatchEntry.
-
-// Use uint64_t to make sure the following data is aligned as 64bit is the
-// strongest alignment requirement.
-
 // C++11 std::max is not constexpr.
 constexpr size_t constexpr_max(size_t x, size_t y) {
   return x > y ? x : y;
@@ -86,38 +69,88 @@
   );
 // clang-format on
 
+// Types needed for the wire format used for communication between the client
+// and heapprofd. The basic format of a record sent by the client is
+// record size (uint64_t) | record type (RecordType = uint64_t) | record
+// If record type is Malloc, the record format is AllocMetdata | raw stack.
+// If the record type is Free, the record is a FreeEntry.
+// If record type is HeapName, the record is a HeapName.
+// On connect, heapprofd sends one ClientConfiguration struct over the control
+// socket.
+
+// Use uint64_t to make sure the following data is aligned as 64bit is the
+// strongest alignment requirement.
+
+struct ClientConfigurationHeap {
+  char name[HEAPPROFD_HEAP_NAME_SZ];
+  uint64_t interval;
+};
+
+struct ClientConfiguration {
+  // On average, sample one allocation every interval bytes,
+  // If interval == 1, sample every allocation.
+  // Must be >= 1.
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) default_interval;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) block_client_timeout_us;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) num_heaps;
+  alignas(8) ClientConfigurationHeap heaps[64];
+  PERFETTO_CROSS_ABI_ALIGNED(bool) block_client;
+  PERFETTO_CROSS_ABI_ALIGNED(bool) disable_fork_teardown;
+  PERFETTO_CROSS_ABI_ALIGNED(bool) disable_vfork_detection;
+  PERFETTO_CROSS_ABI_ALIGNED(bool) all_heaps;
+  // Just double check that the array sizes are in correct order.
+};
+
 enum class RecordType : uint64_t {
   Free = 0,
   Malloc = 1,
+  HeapName = 2,
 };
 
-struct AllocMetadata {
-  uint64_t sequence_number;
+// Make the whole struct 8-aligned. This is to make sizeof(AllocMetdata)
+// the same on 32 and 64-bit.
+struct alignas(8) AllocMetadata {
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) sequence_number;
   // Size of the allocation that was made.
-  uint64_t alloc_size;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) alloc_size;
   // Total number of bytes attributed to this allocation.
-  uint64_t sample_size;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) sample_size;
   // Pointer returned by malloc(2) for this allocation.
-  uint64_t alloc_address;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) alloc_address;
   // Current value of the stack pointer.
-  uint64_t stack_pointer;
-  uint64_t clock_monotonic_coarse_timestamp;
-  alignas(uint64_t) char register_data[kMaxRegisterDataSize];
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) stack_pointer;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) clock_monotonic_coarse_timestamp;
+  // unwindstack::AsmGetRegs assumes this is aligned.
+  alignas(8) char register_data[kMaxRegisterDataSize];
+  PERFETTO_CROSS_ABI_ALIGNED(uint32_t) heap_id;
   // CPU architecture of the client.
-  unwindstack::ArchEnum arch;
-  uint32_t heap_id;
+  PERFETTO_CROSS_ABI_ALIGNED(unwindstack::ArchEnum) arch;
 };
 
 struct FreeEntry {
-  uint64_t sequence_number;
-  uint64_t addr;
-  uint32_t heap_id;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) sequence_number;
+  PERFETTO_CROSS_ABI_ALIGNED(uint64_t) addr;
+  PERFETTO_CROSS_ABI_ALIGNED(uint32_t) heap_id;
 };
 
+struct HeapName {
+  PERFETTO_CROSS_ABI_ALIGNED(uint32_t) heap_id;
+  PERFETTO_CROSS_ABI_ALIGNED(char) heap_name[HEAPPROFD_HEAP_NAME_SZ];
+};
+
+// Make sure the sizes do not change on different architectures.
+static_assert(sizeof(AllocMetadata) == 328,
+              "AllocMetadata needs to be the same size across ABIs.");
+static_assert(sizeof(FreeEntry) == 24,
+              "FreeEntry needs to be the same size across ABIs.");
+static_assert(sizeof(HeapName) == 68,
+              "HeapName needs to be the same size across ABIs.");
+static_assert(sizeof(ClientConfiguration) == 4640,
+              "ClientConfiguration needs to be the same size across ABIs.");
+
 enum HandshakeFDs : size_t {
   kHandshakeMaps = 0,
   kHandshakeMem,
-  kHandshakePageIdle,
   kHandshakeSize,
 };
 
@@ -126,6 +159,7 @@
 
   AllocMetadata* alloc_header;
   FreeEntry* free_header;
+  HeapName* heap_name_header;
 
   char* payload;
   size_t payload_size;
@@ -138,6 +172,9 @@
 // If buf is not a valid message, return false.
 bool ReceiveWireMessage(char* buf, size_t size, WireMessage* out);
 
+uint64_t GetHeapSamplingInterval(const ClientConfiguration& cli_config,
+                                 const char* heap_name);
+
 constexpr const char* kHeapprofdSocketEnvVar = "ANDROID_SOCKET_heapprofd";
 constexpr const char* kHeapprofdSocketFile = "/dev/socket/heapprofd";
 
diff --git a/src/profiling/memory/wire_protocol_unittest.cc b/src/profiling/memory/wire_protocol_unittest.cc
index 9b41dd5..69df58f 100644
--- a/src/profiling/memory/wire_protocol_unittest.cc
+++ b/src/profiling/memory/wire_protocol_unittest.cc
@@ -29,10 +29,14 @@
 
 bool operator==(const AllocMetadata& one, const AllocMetadata& other);
 bool operator==(const AllocMetadata& one, const AllocMetadata& other) {
-  return std::tie(one.sequence_number, one.alloc_size, one.alloc_address,
-                  one.stack_pointer, one.arch) ==
-             std::tie(other.sequence_number, other.alloc_size,
-                      other.alloc_address, other.stack_pointer, other.arch) &&
+  return std::tie(one.sequence_number, one.alloc_size, one.sample_size,
+                  one.alloc_address, one.stack_pointer,
+                  one.clock_monotonic_coarse_timestamp, one.heap_id,
+                  one.arch) == std::tie(other.sequence_number, other.alloc_size,
+                                        other.sample_size, other.alloc_address,
+                                        other.stack_pointer,
+                                        other.clock_monotonic_coarse_timestamp,
+                                        other.heap_id, other.arch) &&
          memcmp(one.register_data, other.register_data, kMaxRegisterDataSize) ==
              0;
 }
@@ -126,6 +130,36 @@
   ASSERT_EQ(recv_msg.payload_size, msg.payload_size);
 }
 
+TEST(GetHeapSamplingInterval, Default) {
+  ClientConfiguration cli_config{};
+  cli_config.all_heaps = true;
+  cli_config.num_heaps = 0;
+  cli_config.default_interval = 4096u;
+  EXPECT_EQ(GetHeapSamplingInterval(cli_config, "something"), 4096u);
+}
+
+TEST(GetHeapSamplingInterval, Selected) {
+  ClientConfiguration cli_config{};
+  cli_config.all_heaps = false;
+  cli_config.num_heaps = 1;
+  cli_config.default_interval = 1;
+  memcpy(cli_config.heaps[0].name, "something", sizeof("something"));
+  cli_config.heaps[0].interval = 4096u;
+  EXPECT_EQ(GetHeapSamplingInterval(cli_config, "something"), 4096u);
+  EXPECT_EQ(GetHeapSamplingInterval(cli_config, "else"), 0u);
+}
+
+TEST(GetHeapSamplingInterval, SelectedAndDefault) {
+  ClientConfiguration cli_config{};
+  cli_config.all_heaps = true;
+  cli_config.num_heaps = 1;
+  cli_config.default_interval = 1;
+  memcpy(cli_config.heaps[0].name, "something", sizeof("something"));
+  cli_config.heaps[0].interval = 4096u;
+  EXPECT_EQ(GetHeapSamplingInterval(cli_config, "something"), 4096u);
+  EXPECT_EQ(GetHeapSamplingInterval(cli_config, "else"), 1u);
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/wrap_allocators.cc b/src/profiling/memory/wrap_allocators.cc
new file mode 100644
index 0000000..0b4debc
--- /dev/null
+++ b/src/profiling/memory/wrap_allocators.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/profiling/memory/heap_profile.h"
+#include "src/profiling/memory/wrap_allocators.h"
+
+namespace perfetto {
+namespace profiling {
+
+namespace {
+size_t RoundUpToSysPageSize(size_t req_size) {
+  const size_t page_size = base::GetSysPageSize();
+  return (req_size + page_size - 1) & ~(page_size - 1);
+}
+}  // namespace
+
+void* wrap_malloc(uint32_t heap_id, void* (*fn)(size_t), size_t size) {
+  void* addr = fn(size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                size);
+  return addr;
+}
+
+void* wrap_calloc(uint32_t heap_id,
+                  void* (*fn)(size_t, size_t),
+                  size_t nmemb,
+                  size_t size) {
+  void* addr = fn(nmemb, size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                nmemb * size);
+  return addr;
+}
+
+void* wrap_memalign(uint32_t heap_id,
+                    void* (*fn)(size_t, size_t),
+                    size_t alignment,
+                    size_t size) {
+  void* addr = fn(alignment, size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                size);
+  return addr;
+}
+
+int wrap_posix_memalign(uint32_t heap_id,
+                        int (*fn)(void**, size_t, size_t),
+                        void** memptr,
+                        size_t alignment,
+                        size_t size) {
+  int res = fn(memptr, alignment, size);
+  if (res != 0)
+    return res;
+
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(*memptr),
+                                size);
+  return 0;
+}
+
+// Note: we record the free before calling the backing implementation to make
+// sure that the address is not reused before we've processed the deallocation
+// (which includes assigning a sequence id to it).
+void wrap_free(uint32_t heap_id, void (*fn)(void*), void* pointer) {
+  // free on a nullptr is valid but has no effect. Short circuit here, for
+  // various advantages:
+  // * More efficient
+  // * Notably printf calls free(nullptr) even when it is used in a way
+  //   malloc-free way, as it unconditionally frees the pointer even if
+  //   it was never written to.
+  //   Short circuiting here makes it less likely to accidentally build
+  //   infinite recursion.
+  if (pointer == nullptr)
+    return;
+
+  AHeapProfile_reportFree(heap_id, reinterpret_cast<uint64_t>(pointer));
+  return fn(pointer);
+}
+
+// As with the free, we record the deallocation before calling the backing
+// implementation to make sure the address is still exclusive while we're
+// processing it.
+void* wrap_realloc(uint32_t heap_id,
+                   void* (*fn)(void*, size_t),
+                   void* pointer,
+                   size_t size) {
+  if (pointer)
+    AHeapProfile_reportFree(heap_id, reinterpret_cast<uint64_t>(pointer));
+  void* addr = fn(pointer, size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                size);
+  return addr;
+}
+
+void* wrap_pvalloc(uint32_t heap_id, void* (*fn)(size_t), size_t size) {
+  void* addr = fn(size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                RoundUpToSysPageSize(size));
+  return addr;
+}
+
+void* wrap_valloc(uint32_t heap_id, void* (*fn)(size_t), size_t size) {
+  void* addr = fn(size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                size);
+  return addr;
+}
+
+void* wrap_reallocarray(uint32_t heap_id,
+                        void* (*fn)(void*, size_t, size_t),
+                        void* pointer,
+                        size_t nmemb,
+                        size_t size) {
+  if (pointer)
+    AHeapProfile_reportFree(heap_id, reinterpret_cast<uint64_t>(pointer));
+  void* addr = fn(pointer, nmemb, size);
+  AHeapProfile_reportAllocation(heap_id, reinterpret_cast<uint64_t>(addr),
+                                nmemb * size);
+  return addr;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/wrap_allocators.h b/src/profiling/memory/wrap_allocators.h
new file mode 100644
index 0000000..ce9fb3e
--- /dev/null
+++ b/src/profiling/memory/wrap_allocators.h
@@ -0,0 +1,57 @@
+/*
+ * 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_PROFILING_MEMORY_WRAP_ALLOCATORS_H_
+#define SRC_PROFILING_MEMORY_WRAP_ALLOCATORS_H_
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+namespace perfetto {
+namespace profiling {
+
+void* wrap_malloc(uint32_t heap_id, void* (*fn)(size_t), size_t size);
+void* wrap_calloc(uint32_t heap_id,
+                  void* (*fn)(size_t, size_t),
+                  size_t nmemb,
+                  size_t size);
+void* wrap_memalign(uint32_t heap_id,
+                    void* (*fn)(size_t, size_t),
+                    size_t alignment,
+                    size_t size);
+int wrap_posix_memalign(uint32_t heap_id,
+                        int (*fn)(void**, size_t, size_t),
+                        void** memptr,
+                        size_t alignment,
+                        size_t size);
+void wrap_free(uint32_t heap_id, void (*fn)(void*), void* pointer);
+void* wrap_realloc(uint32_t heap_id,
+                   void* (*fn)(void*, size_t),
+                   void* pointer,
+                   size_t size);
+void* wrap_pvalloc(uint32_t heap_id, void* (*fn)(size_t), size_t size);
+void* wrap_valloc(uint32_t heap_id, void* (*fn)(size_t), size_t size);
+
+void* wrap_reallocarray(uint32_t heap_id,
+                        void* (*fn)(void*, size_t, size_t),
+                        void* pointer,
+                        size_t nmemb,
+                        size_t size);
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_WRAP_ALLOCATORS_H_
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index b715de1..a1b19ba 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -63,6 +63,7 @@
     "../common:interner",
     "../common:interning_output",
     "../common:proc_utils",
+    "../common:profiler_guardrails",
   ]
   sources = [
     "event_config.cc",
@@ -93,6 +94,7 @@
     "../../../gn:default_deps",
     "../../../include/perfetto/ext/tracing/core",
     "../../../src/base",
+    "../../../src/kallsyms",
     "../common:unwind_support",
   ]
   sources = [
diff --git a/src/profiling/perf/common_types.h b/src/profiling/perf/common_types.h
index 109ff35..f468f10 100644
--- a/src/profiling/perf/common_types.h
+++ b/src/profiling/perf/common_types.h
@@ -49,6 +49,7 @@
   std::unique_ptr<unwindstack::Regs> regs;
   std::vector<char> stack;
   bool stack_maxed = false;
+  std::vector<uint64_t> kernel_ips;
 };
 
 // Entry in an unwinding queue. Either a sample that requires unwinding, or a
@@ -82,7 +83,8 @@
   pid_t pid = 0;
   pid_t tid = 0;
   uint64_t timestamp = 0;
-  std::vector<FrameData> frames;
+  std::vector<unwindstack::FrameData> frames;
+  std::vector<std::string> build_ids;
   unwindstack::ErrorCode unwind_error = unwindstack::ERROR_NONE;
 };
 
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index 06fea9f..8051af7 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -79,6 +79,8 @@
   for (auto it = cfg.exclude_pid(); it; ++it) {
     filter.exclude_pids.insert(*it);
   }
+
+  filter.additional_cmdline_count = cfg.additional_cmdline_count();
   return base::make_optional(std::move(filter));
 }
 
@@ -106,9 +108,14 @@
 // static
 base::Optional<EventConfig> EventConfig::Create(
     const DataSourceConfig& ds_config) {
-  protos::pbzero::PerfEventConfig::Decoder pb_config(
+  protos::pbzero::PerfEventConfig::Decoder event_config_pb(
       ds_config.perf_event_config_raw());
+  return EventConfig::Create(event_config_pb);
+}
 
+// static
+base::Optional<EventConfig> EventConfig::Create(
+    const protos::pbzero::PerfEventConfig::Decoder& pb_config) {
   base::Optional<TargetFilter> filter = ParseTargetFilter(pb_config);
   if (!filter.has_value())
     return base::nullopt;
@@ -162,7 +169,8 @@
       samples_per_tick_limit_(samples_per_tick_limit),
       target_filter_(std::move(target_filter)),
       remote_descriptor_timeout_ms_(remote_descriptor_timeout_ms),
-      unwind_state_clear_period_ms_(cfg.unwind_state_clear_period_ms()) {
+      unwind_state_clear_period_ms_(cfg.unwind_state_clear_period_ms()),
+      kernel_frames_(cfg.kernel_frames()) {
   auto& pe = perf_event_attr_;
   pe.size = sizeof(perf_event_attr);
 
@@ -188,6 +196,12 @@
   // PERF_SAMPLE_REGS_USER:
   pe.sample_regs_user =
       PerfUserRegsMaskForArch(unwindstack::Regs::CurrentArch());
+
+  // Optional kernel callchains:
+  if (kernel_frames_) {
+    pe.sample_type |= PERF_SAMPLE_CALLCHAIN;
+    pe.exclude_callchain_user = true;
+  }
 }
 
 }  // namespace profiling
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
index 27e8915..c667d39 100644
--- a/src/profiling/perf/event_config.h
+++ b/src/profiling/perf/event_config.h
@@ -32,13 +32,14 @@
 namespace perfetto {
 namespace profiling {
 
-// Parsed whitelist/blacklist for filtering samples.
-// An empty whitelist set means that all targets are allowed.
+// Parsed allow/deny-list for filtering samples.
+// An empty filter set means that all targets are allowed.
 struct TargetFilter {
   base::FlatSet<std::string> cmdlines;
   base::FlatSet<std::string> exclude_cmdlines;
   base::FlatSet<pid_t> pids;
   base::FlatSet<pid_t> exclude_pids;
+  uint32_t additional_cmdline_count;
 };
 
 // Describes a single profiling configuration. Bridges the gap between the data
@@ -47,6 +48,8 @@
 class EventConfig {
  public:
   static base::Optional<EventConfig> Create(const DataSourceConfig& ds_config);
+  static base::Optional<EventConfig> Create(
+      const protos::pbzero::PerfEventConfig::Decoder& ds_config);
 
   uint32_t target_all_cpus() const { return target_all_cpus_; }
   uint32_t ring_buffer_pages() const { return ring_buffer_pages_; }
@@ -58,8 +61,8 @@
   uint32_t unwind_state_clear_period_ms() const {
     return unwind_state_clear_period_ms_;
   }
-
   const TargetFilter& filter() const { return target_filter_; }
+  bool kernel_frames() const { return kernel_frames_; }
 
   perf_event_attr* perf_attr() const {
     return const_cast<perf_event_attr*>(&perf_event_attr_);
@@ -91,7 +94,7 @@
   // *each* per-cpu buffer.
   const uint32_t samples_per_tick_limit_;
 
-  // Parsed whitelist/blacklist for filtering samples.
+  // Parsed allow/deny-list for filtering samples.
   const TargetFilter target_filter_;
 
   // Timeout for proc-fd lookup.
@@ -99,6 +102,9 @@
 
   // Optional period for clearing cached unwinder state. Skipped if zero.
   const uint32_t unwind_state_clear_period_ms_;
+
+  // If true, include kernel frames in the callstacks.
+  const bool kernel_frames_;
 };
 
 }  // namespace profiling
diff --git a/src/profiling/perf/event_reader.cc b/src/profiling/perf/event_reader.cc
index 35054f8..f82ef05 100644
--- a/src/profiling/perf/event_reader.cc
+++ b/src/profiling/perf/event_reader.cc
@@ -37,6 +37,13 @@
   return ptr + sizeof(T);
 }
 
+template <typename T>
+const char* ReadValues(T* out, const char* ptr, size_t num_values) {
+  size_t sz = sizeof(T) * num_values;
+  memcpy(out, reinterpret_cast<const void*>(ptr), sz);
+  return ptr + sz;
+}
+
 bool IsPowerOfTwo(size_t v) {
   return (v != 0 && ((v & (v - 1)) == 0));
 }
@@ -281,7 +288,7 @@
                                             const char* record_start) {
   if (event_attr_.sample_type &
       (~uint64_t(PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_STACK_USER |
-                 PERF_SAMPLE_REGS_USER))) {
+                 PERF_SAMPLE_REGS_USER | PERF_SAMPLE_CALLCHAIN))) {
     PERFETTO_FATAL("Unsupported sampling option");
   }
 
@@ -309,6 +316,14 @@
     parse_pos = ReadValue(&sample.timestamp, parse_pos);
   }
 
+  if (event_attr_.sample_type & PERF_SAMPLE_CALLCHAIN) {
+    uint64_t chain_len = 0;
+    parse_pos = ReadValue(&chain_len, parse_pos);
+    sample.kernel_ips.resize(static_cast<size_t>(chain_len));
+    parse_pos = ReadValues<uint64_t>(sample.kernel_ips.data(), parse_pos,
+                                     static_cast<size_t>(chain_len));
+  }
+
   if (event_attr_.sample_type & PERF_SAMPLE_REGS_USER) {
     // Can be empty, e.g. if we sampled a kernel thread.
     sample.regs = ReadPerfUserRegsData(&parse_pos);
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index e0dde42..2b9f101 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -19,7 +19,6 @@
 #include <random>
 #include <utility>
 
-#include <malloc.h>
 #include <unistd.h>
 
 #include <unwindstack/Error.h>
@@ -28,6 +27,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/base/metatrace.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/producer.h"
@@ -37,6 +37,7 @@
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "src/profiling/common/callstack_trie.h"
 #include "src/profiling/common/proc_utils.h"
+#include "src/profiling/common/profiler_guardrails.h"
 #include "src/profiling/common/unwind_support.h"
 #include "src/profiling/perf/common_types.h"
 #include "src/profiling/perf/event_reader.h"
@@ -62,6 +63,8 @@
 // The proper fix is in the platform, see bug for progress.
 constexpr uint32_t kProcDescriptorsAndroidDelayMs = 50;
 
+constexpr uint32_t kMemoryLimitCheckPeriodMs = 5 * 1000;
+
 constexpr uint32_t kInitialConnectionBackoffMs = 100;
 constexpr uint32_t kMaxConnectionBackoffMs = 30 * 1000;
 
@@ -85,41 +88,54 @@
   return period_ms - ((now_ms - ds_period_offset) % period_ms);
 }
 
-bool ShouldRejectDueToFilter(pid_t pid, const TargetFilter& filter) {
-  bool reject_cmd = false;
+bool ShouldRejectDueToFilter(pid_t pid,
+                             base::FlatSet<std::string>* additional_cmdlines,
+                             const TargetFilter& filter) {
+  PERFETTO_CHECK(additional_cmdlines);
   std::string cmdline;
-  if (GetCmdlineForPID(pid, &cmdline)) {  // normalized form
-    // reject if absent from non-empty whitelist, or present in blacklist
-    reject_cmd = (filter.cmdlines.size() && !filter.cmdlines.count(cmdline)) ||
-                 filter.exclude_cmdlines.count(cmdline);
-  } else {
+  bool have_cmdline = GetCmdlineForPID(pid, &cmdline);  // normalized form
+  if (!have_cmdline) {
     PERFETTO_DLOG("Failed to look up cmdline for pid [%d]",
                   static_cast<int>(pid));
-    // reject only if there's a whitelist present
-    reject_cmd = filter.cmdlines.size() > 0;
   }
 
-  bool reject_pid = (filter.pids.size() && !filter.pids.count(pid)) ||
-                    filter.exclude_pids.count(pid);
-
-  if (reject_cmd || reject_pid) {
-    PERFETTO_DLOG(
-        "Rejecting samples for pid [%d] due to cmdline(%d) or pid(%d)",
-        static_cast<int>(pid), reject_cmd, reject_pid);
-
+  if (have_cmdline && filter.exclude_cmdlines.count(cmdline)) {
+    PERFETTO_DLOG("Explicitly rejecting samples for pid [%d] due to cmdline",
+                  static_cast<int>(pid));
     return true;
   }
-  return false;
-}
+  if (filter.exclude_pids.count(pid)) {
+    PERFETTO_DLOG("Explicitly rejecting samples for pid [%d] due to pid",
+                  static_cast<int>(pid));
+    return true;
+  }
 
-void MaybeReleaseAllocatorMemToOS() {
-#if defined(__BIONIC__)
-  // TODO(b/152414415): libunwindstack's volume of small allocations is
-  // adverarial to scudo, which doesn't automatically release small
-  // allocation regions back to the OS. Forceful purge does reclaim all size
-  // classes.
-  mallopt(M_PURGE, 0);
-#endif
+  if (have_cmdline && filter.cmdlines.count(cmdline)) {
+    return false;
+  }
+  if (filter.pids.count(pid)) {
+    return false;
+  }
+  if (!filter.cmdlines.size() && !filter.pids.size() &&
+      !filter.additional_cmdline_count) {
+    // If no filters are set allow everything.
+    return false;
+  }
+
+  // If we didn't read the command line that's a good prediction we will not be
+  // able to profile either.
+  if (have_cmdline) {
+    if (additional_cmdlines->count(cmdline)) {
+      return false;
+    }
+    if (additional_cmdlines->size() < filter.additional_cmdline_count) {
+      additional_cmdlines->insert(cmdline);
+      return false;
+    }
+  }
+
+  PERFETTO_DLOG("Rejecting samples for pid [%d]", static_cast<int>(pid));
+  return true;
 }
 
 protos::pbzero::Profiling::CpuMode ToCpuModeEnum(uint16_t perf_cpu_mode) {
@@ -160,6 +176,12 @@
       return Profiling::UNWIND_ERROR_REPEATED_FRAME;
     case unwindstack::ERROR_INVALID_ELF:
       return Profiling::UNWIND_ERROR_INVALID_ELF;
+    case unwindstack::ERROR_SYSTEM_CALL:
+      return Profiling::UNWIND_ERROR_SYSTEM_CALL;
+    case unwindstack::ERROR_THREAD_TIMEOUT:
+      return Profiling::UNWIND_ERROR_THREAD_TIMEOUT;
+    case unwindstack::ERROR_THREAD_DOES_NOT_EXIST:
+      return Profiling::UNWIND_ERROR_THREAD_DOES_NOT_EXIST;
   }
   return Profiling::UNWIND_ERROR_UNKNOWN;
 }
@@ -179,14 +201,13 @@
 void PerfProducer::SetupDataSource(DataSourceInstanceID,
                                    const DataSourceConfig&) {}
 
-void PerfProducer::StartDataSource(DataSourceInstanceID instance_id,
+void PerfProducer::StartDataSource(DataSourceInstanceID ds_id,
                                    const DataSourceConfig& config) {
-  PERFETTO_LOG("StartDataSource(%zu, %s)", static_cast<size_t>(instance_id),
+  PERFETTO_LOG("StartDataSource(%zu, %s)", static_cast<size_t>(ds_id),
                config.name().c_str());
 
   if (config.name() == MetatraceWriter::kDataSourceName) {
-    StartMetatraceSource(instance_id,
-                         static_cast<BufferID>(config.target_buffer()));
+    StartMetatraceSource(ds_id, static_cast<BufferID>(config.target_buffer()));
     return;
   }
 
@@ -194,7 +215,10 @@
   if (config.name() != kDataSourceName)
     return;
 
-  base::Optional<EventConfig> event_config = EventConfig::Create(config);
+  protos::pbzero::PerfEventConfig::Decoder event_config_pb(
+      config.perf_event_config_raw());
+  base::Optional<EventConfig> event_config =
+      EventConfig::Create(event_config_pb);
   if (!event_config.has_value()) {
     PERFETTO_ELOG("PerfEventConfig rejected.");
     return;
@@ -226,7 +250,7 @@
   std::map<DataSourceInstanceID, DataSourceState>::iterator ds_it;
   bool inserted;
   std::tie(ds_it, inserted) = data_sources_.emplace(
-      std::piecewise_construct, std::forward_as_tuple(instance_id),
+      std::piecewise_construct, std::forward_as_tuple(ds_id),
       std::forward_as_tuple(event_config.value(), std::move(writer),
                             std::move(per_cpu_readers)));
   PERFETTO_CHECK(inserted);
@@ -238,38 +262,81 @@
 
   // Inform unwinder of the new data source instance, and optionally start a
   // periodic task to clear its cached state.
-  unwinding_worker_->PostStartDataSource(instance_id);
+  unwinding_worker_->PostStartDataSource(ds_id,
+                                         ds.event_config.kernel_frames());
   if (ds.event_config.unwind_state_clear_period_ms()) {
     unwinding_worker_->PostClearCachedStatePeriodic(
-        instance_id, ds.event_config.unwind_state_clear_period_ms());
+        ds_id, ds.event_config.unwind_state_clear_period_ms());
   }
 
   // Kick off periodic read task.
   auto tick_period_ms = ds.event_config.read_tick_period_ms();
   auto weak_this = weak_factory_.GetWeakPtr();
   task_runner_->PostDelayedTask(
-      [weak_this, instance_id] {
+      [weak_this, ds_id] {
         if (weak_this)
-          weak_this->TickDataSourceRead(instance_id);
+          weak_this->TickDataSourceRead(ds_id);
       },
-      TimeToNextReadTickMs(instance_id, tick_period_ms));
+      TimeToNextReadTickMs(ds_id, tick_period_ms));
+
+  // Optionally kick off periodic memory footprint limit check.
+  uint32_t max_daemon_memory_kb = event_config_pb.max_daemon_memory_kb();
+  if (max_daemon_memory_kb > 0) {
+    task_runner_->PostDelayedTask(
+        [weak_this, ds_id, max_daemon_memory_kb] {
+          if (weak_this)
+            weak_this->CheckMemoryFootprintPeriodic(ds_id,
+                                                    max_daemon_memory_kb);
+        },
+        kMemoryLimitCheckPeriodMs);
+  }
 }
 
-void PerfProducer::StopDataSource(DataSourceInstanceID instance_id) {
-  PERFETTO_LOG("StopDataSource(%zu)", static_cast<size_t>(instance_id));
+void PerfProducer::CheckMemoryFootprintPeriodic(DataSourceInstanceID ds_id,
+                                                uint32_t max_daemon_memory_kb) {
+  auto ds_it = data_sources_.find(ds_id);
+  if (ds_it == data_sources_.end())
+    return;  // stop recurring
+
+  GuardrailConfig gconfig = {};
+  gconfig.memory_guardrail_kb = max_daemon_memory_kb;
+
+  ProfilerMemoryGuardrails footprint_snapshot;
+  if (footprint_snapshot.IsOverMemoryThreshold(gconfig)) {
+    PurgeDataSource(ds_id);
+    return;  // stop recurring
+  }
+
+  // repost
+  auto weak_this = weak_factory_.GetWeakPtr();
+  task_runner_->PostDelayedTask(
+      [weak_this, ds_id, max_daemon_memory_kb] {
+        if (weak_this)
+          weak_this->CheckMemoryFootprintPeriodic(ds_id, max_daemon_memory_kb);
+      },
+      kMemoryLimitCheckPeriodMs);
+}
+
+void PerfProducer::StopDataSource(DataSourceInstanceID ds_id) {
+  PERFETTO_LOG("StopDataSource(%zu)", static_cast<size_t>(ds_id));
 
   // Metatrace: stop immediately (will miss the events from the
   // asynchronous shutdown of the primary data source).
-  auto meta_it = metatrace_writers_.find(instance_id);
+  auto meta_it = metatrace_writers_.find(ds_id);
   if (meta_it != metatrace_writers_.end()) {
     meta_it->second.WriteAllAndFlushTraceWriter([] {});
     metatrace_writers_.erase(meta_it);
     return;
   }
 
-  auto ds_it = data_sources_.find(instance_id);
-  if (ds_it == data_sources_.end())
+  auto ds_it = data_sources_.find(ds_id);
+  if (ds_it == data_sources_.end()) {
+    // Most likely, the source is missing due to an abrupt stop (via
+    // |PurgeDataSource|). Tell the service that we've stopped the source now,
+    // so that it doesn't wait for the ack until the timeout.
+    endpoint_->NotifyDataSourceStopped(ds_id);
     return;
+  }
 
   // Start shutting down the reading frontend, which will propagate the stop
   // further as the intermediate buffers are cleared.
@@ -285,7 +352,7 @@
 void PerfProducer::Flush(FlushRequestID flush_id,
                          const DataSourceInstanceID* data_source_ids,
                          size_t num_data_sources) {
-  bool should_ack_flush = false;
+  // Flush metatracing if requested.
   for (size_t i = 0; i < num_data_sources; i++) {
     auto ds_id = data_source_ids[i];
     PERFETTO_DLOG("Flush(%zu)", static_cast<size_t>(ds_id));
@@ -293,14 +360,10 @@
     auto meta_it = metatrace_writers_.find(ds_id);
     if (meta_it != metatrace_writers_.end()) {
       meta_it->second.WriteAllAndFlushTraceWriter([] {});
-      should_ack_flush = true;
-    }
-    if (data_sources_.find(ds_id) != data_sources_.end()) {
-      should_ack_flush = true;
     }
   }
-  if (should_ack_flush)
-    endpoint_->NotifyFlushComplete(flush_id);
+
+  endpoint_->NotifyFlushComplete(flush_id);
 }
 
 void PerfProducer::ClearIncrementalState(
@@ -427,9 +490,9 @@
       PERFETTO_DLOG("New pid: [%d]", static_cast<int>(pid));
 
       // Check whether samples for this new process should be
-      // dropped due to the target whitelist/blacklist.
+      // dropped due to the target filtering.
       const TargetFilter& filter = ds->event_config.filter();
-      if (ShouldRejectDueToFilter(pid, filter)) {
+      if (ShouldRejectDueToFilter(pid, &ds->additional_cmdlines, filter)) {
         process_state = ProcessTrackingStatus::kRejected;
         continue;
       }
@@ -567,12 +630,16 @@
 void PerfProducer::EmitSample(DataSourceInstanceID ds_id,
                               CompletedSample sample) {
   auto ds_it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(ds_it != data_sources_.end());
+  if (ds_it == data_sources_.end()) {
+    PERFETTO_DLOG("EmitSample(ds: %zu): source gone",
+                  static_cast<size_t>(ds_id));
+    return;
+  }
   DataSourceState& ds = ds_it->second;
 
   // intern callsite
   GlobalCallstackTrie::Node* callstack_root =
-      callstack_trie_.CreateCallsite(sample.frames);
+      callstack_trie_.CreateCallsite(sample.frames, sample.build_ids);
   uint64_t callstack_iid = callstack_root->id();
 
   // start packet
@@ -600,7 +667,8 @@
                                       size_t cpu,
                                       uint64_t records_lost) {
   auto ds_it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(ds_it != data_sources_.end());
+  if (ds_it == data_sources_.end())
+    return;
   DataSourceState& ds = ds_it->second;
   PERFETTO_DLOG("DataSource(%zu): cpu%zu lost [%" PRIu64 "] records",
                 static_cast<size_t>(ds_id), cpu, records_lost);
@@ -642,7 +710,8 @@
                                      ParsedSample sample,
                                      SampleSkipReason reason) {
   auto ds_it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(ds_it != data_sources_.end());
+  if (ds_it == data_sources_.end())
+    return;
   DataSourceState& ds = ds_it->second;
 
   auto packet = ds.trace_writer->NewTracePacket();
@@ -691,7 +760,11 @@
 void PerfProducer::FinishDataSourceStop(DataSourceInstanceID ds_id) {
   PERFETTO_LOG("FinishDataSourceStop(%zu)", static_cast<size_t>(ds_id));
   auto ds_it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(ds_it != data_sources_.end());
+  if (ds_it == data_sources_.end()) {
+    PERFETTO_DLOG("FinishDataSourceStop(%zu): source gone",
+                  static_cast<size_t>(ds_id));
+    return;
+  }
   DataSourceState& ds = ds_it->second;
   PERFETTO_CHECK(ds.status == DataSourceState::Status::kShuttingDown);
 
@@ -703,7 +776,47 @@
   // Clean up resources if there are no more active sources.
   if (data_sources_.empty()) {
     callstack_trie_.ClearTrie();  // purge internings
-    MaybeReleaseAllocatorMemToOS();
+    base::MaybeReleaseAllocatorMemToOS();
+  }
+}
+
+// TODO(rsavitski): maybe make the tracing service respect premature
+// producer-driven stops, and then issue a NotifyDataSourceStopped here.
+// Alternatively (and at the expense of higher complexity) introduce a new data
+// source status of "tombstoned", and propagate it until the source is stopped
+// by the service (this would technically allow for stricter lifetime checking
+// of data sources, and help with discarding periodic flushes).
+// TODO(rsavitski): Purging while stopping will currently leave the stop
+// unacknowledged. Consider checking whether the DS is stopping here, and if so,
+// notifying immediately after erasing.
+void PerfProducer::PurgeDataSource(DataSourceInstanceID ds_id) {
+  auto ds_it = data_sources_.find(ds_id);
+  if (ds_it == data_sources_.end())
+    return;
+  DataSourceState& ds = ds_it->second;
+
+  PERFETTO_LOG("Stopping DataSource(%zu) prematurely",
+               static_cast<size_t>(ds_id));
+
+  unwinding_worker_->PostPurgeDataSource(ds_id);
+
+  // Write a packet indicating the abrupt stop.
+  {
+    auto packet = ds.trace_writer->NewTracePacket();
+    packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
+    auto* perf_sample = packet->set_perf_sample();
+    auto* producer_event = perf_sample->set_producer_event();
+    producer_event->set_source_stop_reason(
+        protos::pbzero::PerfSample::ProducerEvent::PROFILER_STOP_GUARDRAIL);
+  }
+
+  ds.trace_writer->Flush();
+  data_sources_.erase(ds_it);
+
+  // Clean up resources if there are no more active sources.
+  if (data_sources_.empty()) {
+    callstack_trie_.ClearTrie();  // purge internings
+    base::MaybeReleaseAllocatorMemToOS();
   }
 }
 
diff --git a/src/profiling/perf/perf_producer.h b/src/profiling/perf/perf_producer.h
index 25fc0d2..5241cb8 100644
--- a/src/profiling/perf/perf_producer.h
+++ b/src/profiling/perf/perf_producer.h
@@ -138,6 +138,10 @@
     // in the |Unwinder|, which needs to track whether the necessary unwinding
     // inputs for a given process' samples are ready.
     std::map<pid_t, ProcessTrackingStatus> process_states;
+
+    // Command lines we have decided to unwind, up to a total of
+    // additional_cmdline_count values.
+    base::FlatSet<std::string> additional_cmdlines;
   };
 
   // For |EmitSkippedSample|.
@@ -198,6 +202,15 @@
   // Destroys the state belonging to this instance, and acks the stop to the
   // tracing service.
   void FinishDataSourceStop(DataSourceInstanceID ds_id);
+  // Immediately destroys the data source state, and instructs the unwinder to
+  // do the same. This is used for abrupt stops.
+  void PurgeDataSource(DataSourceInstanceID ds_id);
+
+  // Immediately stops the data source if this daemon's overall memory footprint
+  // is above the given threshold. This periodic task is started only for data
+  // sources that specify a limit.
+  void CheckMemoryFootprintPeriodic(DataSourceInstanceID ds_id,
+                                    uint32_t max_daemon_memory_kb);
 
   void StartMetatraceSource(DataSourceInstanceID ds_id, BufferID target_buffer);
 
diff --git a/src/profiling/perf/proc_descriptors.cc b/src/profiling/perf/proc_descriptors.cc
index b028d85..d5ab822 100644
--- a/src/profiling/perf/proc_descriptors.cc
+++ b/src/profiling/perf/proc_descriptors.cc
@@ -16,6 +16,7 @@
 
 #include "src/profiling/perf/proc_descriptors.h"
 
+#include <fcntl.h>
 #include <signal.h>
 #include <sys/stat.h>
 #include <unistd.h>
diff --git a/src/profiling/perf/unwinding.cc b/src/profiling/perf/unwinding.cc
index 5000f1e..c6549e4 100644
--- a/src/profiling/perf/unwinding.cc
+++ b/src/profiling/perf/unwinding.cc
@@ -20,23 +20,15 @@
 
 #include <inttypes.h>
 
+#include <unwindstack/Unwinder.h>
+
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/base/thread_utils.h"
+#include "perfetto/ext/base/utils.h"
 
 namespace {
 constexpr size_t kUnwindingMaxFrames = 1000;
 constexpr uint32_t kDataSourceShutdownRetryDelayMs = 400;
-
-void MaybeReleaseAllocatorMemToOS() {
-#if defined(__BIONIC__)
-  // TODO(b/152414415): libunwindstack's volume of small allocations is
-  // adverarial to scudo, which doesn't automatically release small
-  // allocation regions back to the OS. Forceful purge does reclaim all size
-  // classes.
-  mallopt(M_PURGE, 0);
-#endif
-}
-
 }  // namespace
 
 namespace perfetto {
@@ -50,18 +42,24 @@
   base::MaybeSetThreadName("stack-unwinding");
 }
 
-void Unwinder::PostStartDataSource(DataSourceInstanceID ds_id) {
+void Unwinder::PostStartDataSource(DataSourceInstanceID ds_id,
+                                   bool kernel_frames) {
   // No need for a weak pointer as the associated task runner quits (stops
   // running tasks) strictly before the Unwinder's destruction.
-  task_runner_->PostTask([this, ds_id] { StartDataSource(ds_id); });
+  task_runner_->PostTask(
+      [this, ds_id, kernel_frames] { StartDataSource(ds_id, kernel_frames); });
 }
 
-void Unwinder::StartDataSource(DataSourceInstanceID ds_id) {
+void Unwinder::StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DLOG("Unwinder::StartDataSource(%zu)", static_cast<size_t>(ds_id));
 
   auto it_and_inserted = data_sources_.emplace(ds_id, DataSourceState{});
   PERFETTO_DCHECK(it_and_inserted.second);
+
+  if (kernel_frames) {
+    kernel_symbolizer_.GetOrCreateKernelSymbolMap();
+  }
 }
 
 // c++11: use shared_ptr to transfer resource handles, so that the resources get
@@ -102,7 +100,8 @@
                 maps_fd.get(), mem_fd.get());
 
   auto it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(it != data_sources_.end());
+  if (it == data_sources_.end())
+    return;
   DataSourceState& ds = it->second;
 
   ProcessState& proc_state = ds.process_states[pid];  // insert if new
@@ -129,7 +128,8 @@
                 static_cast<size_t>(ds_id), static_cast<int>(pid));
 
   auto it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(it != data_sources_.end());
+  if (it == data_sources_.end())
+    return;
   DataSourceState& ds = it->second;
 
   ProcessState& proc_state = ds.process_states[pid];  // insert if new
@@ -212,8 +212,12 @@
     if (!entry.valid)
       continue;  // already processed
 
+    // Data source might be gone due to an abrupt stop.
     auto it = data_sources_.find(entry.data_source_id);
-    PERFETTO_CHECK(it != data_sources_.end());
+    if (it == data_sources_.end()) {
+      entry = UnwindEntry::Invalid();
+      continue;
+    }
     DataSourceState& ds = it->second;
 
     pid_t pid = entry.sample.pid;
@@ -305,11 +309,13 @@
 
   struct UnwindResult {
     unwindstack::ErrorCode error_code;
+    uint64_t warnings;
     std::vector<unwindstack::FrameData> frames;
 
     UnwindResult(unwindstack::ErrorCode e,
+                 uint64_t w,
                  std::vector<unwindstack::FrameData> f)
-        : error_code(e), frames(std::move(f)) {}
+        : error_code(e), warnings(w), frames(std::move(f)) {}
     UnwindResult(const UnwindResult&) = delete;
     UnwindResult& operator=(const UnwindResult&) = delete;
     UnwindResult(UnwindResult&&) = default;
@@ -328,17 +334,21 @@
     unwindstack::Unwinder unwinder(kUnwindingMaxFrames, &unwind_state->fd_maps,
                                    regs_copy.get(), overlay_memory);
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
-    unwinder.SetJitDebug(unwind_state->jit_debug.get(), regs_copy->Arch());
-    unwinder.SetDexFiles(unwind_state->dex_files.get(), regs_copy->Arch());
+    unwinder.SetJitDebug(unwind_state->jit_debug.get());
+    unwinder.SetDexFiles(unwind_state->dex_files.get());
 #endif
     unwinder.Unwind(/*initial_map_names_to_skip=*/nullptr,
                     /*map_suffixes_to_ignore=*/nullptr);
-    return {unwinder.LastErrorCode(), unwinder.ConsumeFrames()};
+    return {unwinder.LastErrorCode(), unwinder.warnings(),
+            unwinder.ConsumeFrames()};
   };
 
   // first unwind attempt
   UnwindResult unwind = attempt_unwind();
 
+  bool should_retry = unwind.error_code == unwindstack::ERROR_INVALID_MAP ||
+                      unwind.warnings & unwindstack::WARNING_DEX_PC_NOT_IN_MAP;
+
   // ERROR_INVALID_MAP means that unwinding reached a point in memory without a
   // corresponding mapping. This is possible if the parsed /proc/pid/maps is
   // outdated. Reparse and try again.
@@ -348,11 +358,10 @@
   // error around the truncated part is not unexpected.
   //
   // TODO(rsavitski): consider rate-limiting unwind retries.
-  if (unwind.error_code == unwindstack::ERROR_INVALID_MAP &&
-      sample.stack_maxed) {
+  if (should_retry && sample.stack_maxed) {
     PERFETTO_DLOG("Skipping reparse/reunwind due to maxed stack for tid [%d]",
                   static_cast<int>(sample.tid));
-  } else if (unwind.error_code == unwindstack::ERROR_INVALID_MAP) {
+  } else if (should_retry) {
     {
       PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, PROFILER_MAPS_REPARSE);
       PERFETTO_DLOG("Reparsing maps for pid [%d]",
@@ -363,11 +372,27 @@
     unwind = attempt_unwind();
   }
 
-  ret.frames.reserve(unwind.frames.size());
+  // Symbolize kernel-unwound kernel frames (if any).
+  std::vector<unwindstack::FrameData> kernel_frames =
+      SymbolizeKernelCallchain(sample);
+
+  // Concatenate the kernel and userspace frames.
+  auto kernel_frames_size = kernel_frames.size();
+
+  ret.frames = std::move(kernel_frames);
+
+  ret.build_ids.reserve(kernel_frames_size + unwind.frames.size());
+  ret.frames.reserve(kernel_frames_size + unwind.frames.size());
+
+  ret.build_ids.resize(kernel_frames_size, "");
+
   for (unwindstack::FrameData& frame : unwind.frames) {
-    ret.frames.emplace_back(unwind_state->AnnotateFrame(std::move(frame)));
+    ret.build_ids.emplace_back(unwind_state->GetBuildId(frame));
+    ret.frames.emplace_back(std::move(frame));
   }
 
+  PERFETTO_CHECK(ret.build_ids.size() == ret.frames.size());
+
   // In case of an unwinding error, add a synthetic error frame (which will
   // appear as a caller of the partially-unwound fragment), for easier
   // visualization of errors.
@@ -377,13 +402,47 @@
     frame_data.function_name =
         "ERROR " + StringifyLibUnwindstackError(unwind.error_code);
     frame_data.map_name = "ERROR";
-    ret.frames.emplace_back(std::move(frame_data), /*build_id=*/"");
+    ret.frames.emplace_back(std::move(frame_data));
+    ret.build_ids.emplace_back("");
     ret.unwind_error = unwind.error_code;
   }
 
   return ret;
 }
 
+std::vector<unwindstack::FrameData> Unwinder::SymbolizeKernelCallchain(
+    const ParsedSample& sample) {
+  std::vector<unwindstack::FrameData> ret;
+  if (sample.kernel_ips.empty())
+    return ret;
+
+  // The list of addresses contains special context marker values (inserted by
+  // the kernel's unwinding) to indicate which section of the callchain belongs
+  // to the kernel/user mode (if the kernel can successfully unwind user
+  // stacks). In our case, we request only the kernel frames.
+  if (sample.kernel_ips[0] != PERF_CONTEXT_KERNEL) {
+    PERFETTO_DFATAL_OR_ELOG(
+        "Unexpected: 0th frame of callchain is not PERF_CONTEXT_KERNEL.");
+    return ret;
+  }
+
+  auto* kernel_map = kernel_symbolizer_.GetOrCreateKernelSymbolMap();
+  PERFETTO_DCHECK(kernel_map);
+  ret.reserve(sample.kernel_ips.size());
+  for (size_t i = 1; i < sample.kernel_ips.size(); i++) {
+    std::string function_name = kernel_map->Lookup(sample.kernel_ips[i]);
+
+    // Synthesise a partially-valid libunwindstack frame struct for the kernel
+    // frame. We reuse the type for convenience. The kernel frames are marked by
+    // a magical "kernel" string as their containing mapping.
+    unwindstack::FrameData frame{};
+    frame.function_name = std::move(function_name);
+    frame.map_name = "kernel";
+    ret.emplace_back(std::move(frame));
+  }
+  return ret;
+}
+
 void Unwinder::PostInitiateDataSourceStop(DataSourceInstanceID ds_id) {
   task_runner_->PostTask([this, ds_id] { InitiateDataSourceStop(ds_id); });
 }
@@ -394,7 +453,8 @@
                 static_cast<size_t>(ds_id));
 
   auto it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(it != data_sources_.end());
+  if (it == data_sources_.end())
+    return;
   DataSourceState& ds = it->second;
 
   PERFETTO_CHECK(ds.status == DataSourceState::Status::kActive);
@@ -411,7 +471,8 @@
                 static_cast<size_t>(ds_id));
 
   auto it = data_sources_.find(ds_id);
-  PERFETTO_CHECK(it != data_sources_.end());
+  if (it == data_sources_.end())
+    return;
   DataSourceState& ds = it->second;
 
   // Drop unwinder's state tied to the source.
@@ -419,13 +480,40 @@
   data_sources_.erase(it);
 
   // Clean up state if there are no more active sources.
-  if (data_sources_.empty())
+  if (data_sources_.empty()) {
+    kernel_symbolizer_.Destroy();
     ResetAndEnableUnwindstackCache();
+  }
 
   // Inform service thread that the unwinder is done with the source.
   delegate_->PostFinishDataSourceStop(ds_id);
 }
 
+void Unwinder::PostPurgeDataSource(DataSourceInstanceID ds_id) {
+  task_runner_->PostTask([this, ds_id] { PurgeDataSource(ds_id); });
+}
+
+void Unwinder::PurgeDataSource(DataSourceInstanceID ds_id) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  PERFETTO_DLOG("Unwinder::PurgeDataSource(%zu)", static_cast<size_t>(ds_id));
+
+  auto it = data_sources_.find(ds_id);
+  if (it == data_sources_.end())
+    return;
+
+  data_sources_.erase(it);
+
+  // Clean up state if there are no more active sources.
+  if (data_sources_.empty()) {
+    kernel_symbolizer_.Destroy();
+    ResetAndEnableUnwindstackCache();
+    // Also purge scudo on Android, which would normally be done by the service
+    // thread in |FinishDataSourceStop|. This is important as most of the scudo
+    // overhead comes from libunwindstack.
+    base::MaybeReleaseAllocatorMemToOS();
+  }
+}
+
 void Unwinder::PostClearCachedStatePeriodic(DataSourceInstanceID ds_id,
                                             uint32_t period_ms) {
   task_runner_->PostDelayedTask(
@@ -451,7 +539,7 @@
     pid_and_process.second.unwind_state->fd_maps.Reset();
   }
   ResetAndEnableUnwindstackCache();
-  MaybeReleaseAllocatorMemToOS();
+  base::MaybeReleaseAllocatorMemToOS();
 
   PostClearCachedStatePeriodic(ds_id, period_ms);  // repost
 }
diff --git a/src/profiling/perf/unwinding.h b/src/profiling/perf/unwinding.h
index 46c3760..650e721 100644
--- a/src/profiling/perf/unwinding.h
+++ b/src/profiling/perf/unwinding.h
@@ -31,6 +31,8 @@
 #include "perfetto/ext/base/thread_checker.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
+#include "src/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/lazy_kernel_symbolizer.h"
 #include "src/profiling/common/unwind_support.h"
 #include "src/profiling/perf/common_types.h"
 #include "src/profiling/perf/unwind_queue.h"
@@ -85,7 +87,7 @@
 
   ~Unwinder() { PERFETTO_DCHECK_THREAD(thread_checker_); }
 
-  void PostStartDataSource(DataSourceInstanceID ds_id);
+  void PostStartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
   void PostAdoptProcDescriptors(DataSourceInstanceID ds_id,
                                 pid_t pid,
                                 base::ScopedFile maps_fd,
@@ -93,6 +95,7 @@
   void PostRecordTimedOutProcDescriptors(DataSourceInstanceID ds_id, pid_t pid);
   void PostProcessQueue();
   void PostInitiateDataSourceStop(DataSourceInstanceID ds_id);
+  void PostPurgeDataSource(DataSourceInstanceID ds_id);
 
   void PostClearCachedStatePeriodic(DataSourceInstanceID ds_id,
                                     uint32_t period_ms);
@@ -128,7 +131,8 @@
   Unwinder(Delegate* delegate, base::UnixTaskRunner* task_runner);
 
   // Marks the data source as valid and active at the unwinding stage.
-  void StartDataSource(DataSourceInstanceID ds_id);
+  // Initializes kernel address symbolization if needed.
+  void StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
 
   void AdoptProcDescriptors(DataSourceInstanceID ds_id,
                             pid_t pid,
@@ -149,6 +153,10 @@
                                UnwindingMetadata* unwind_state,
                                bool pid_unwound_before);
 
+  // Returns a list of symbolized kernel frames in the sample (if any).
+  std::vector<unwindstack::FrameData> SymbolizeKernelCallchain(
+      const ParsedSample& sample);
+
   // Marks the data source as shutting down at the unwinding stage. It is known
   // that no new samples for this source will be pushed into the queue, but we
   // need to delay the unwinder state teardown until all previously-enqueued
@@ -160,6 +168,9 @@
   // sequence.
   void FinishDataSourceStop(DataSourceInstanceID ds_id);
 
+  // Immediately destroys the data source state, used for abrupt stops.
+  void PurgeDataSource(DataSourceInstanceID ds_id);
+
   // Clears the parsed maps for all previously-sampled processes, and resets the
   // libunwindstack cache. This has the effect of deallocating the cached Elf
   // objects within libunwindstack, which take up non-trivial amounts of memory.
@@ -196,6 +207,7 @@
   Delegate* const delegate_;
   UnwindQueue<UnwindEntry, kUnwindQueueCapacity> unwind_queue_;
   std::map<DataSourceInstanceID, DataSourceState> data_sources_;
+  LazyKernelSymbolizer kernel_symbolizer_;
 
   PERFETTO_THREAD_CHECKER(thread_checker_)
 };
@@ -206,7 +218,7 @@
 // owned state, and consolidate.
 class UnwinderHandle {
  public:
-  UnwinderHandle(Unwinder::Delegate* delegate) {
+  explicit UnwinderHandle(Unwinder::Delegate* delegate) {
     std::mutex init_lock;
     std::condition_variable init_cv;
 
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 79c770e..6989d0a 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -19,8 +19,17 @@
   public_deps = [ "../../../include/perfetto/ext/base" ]
   deps = [ "../../../gn:default_deps" ]
   sources = [
+    "filesystem.h",
+    "filesystem_posix.cc",
+    "filesystem_windows.cc",
     "local_symbolizer.cc",
     "local_symbolizer.h",
+    "scoped_read_mmap.h",
+    "scoped_read_mmap_posix.cc",
+    "scoped_read_mmap_windows.cc",
+    "subprocess.h",
+    "subprocess_posix.cc",
+    "subprocess_windows.cc",
     "symbolizer.cc",
     "symbolizer.h",
   ]
@@ -50,6 +59,7 @@
     ":symbolizer",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
+    "../../base:test_support",
   ]
   sources = [ "local_symbolizer_unittest.cc" ]
 }
diff --git a/src/profiling/memory/client_ext_factory.h b/src/profiling/symbolizer/filesystem.h
similarity index 63%
copy from src/profiling/memory/client_ext_factory.h
copy to src/profiling/symbolizer/filesystem.h
index fee7767..d9cf87c 100644
--- a/src/profiling/memory/client_ext_factory.h
+++ b/src/profiling/symbolizer/filesystem.h
@@ -14,23 +14,19 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
-#define SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+#ifndef SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
+#define SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
 
-#include <memory>
-
-#include "src/profiling/memory/client.h"
-#include "src/profiling/memory/unhooked_allocator.h"
+#include "src/profiling/symbolizer/local_symbolizer.h"
 
 namespace perfetto {
 namespace profiling {
 
-void StartHeapprofdIfStatic();
-
-std::shared_ptr<Client> ConstructClient(
-    UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator);
+using FileCallback = std::function<void(const char*, size_t)>;
+size_t GetFileSize(const std::string& file_path);
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn);
 
 }  // namespace profiling
 }  // namespace perfetto
 
-#endif  // SRC_PROFILING_MEMORY_CLIENT_EXT_FACTORY_H_
+#endif  // SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
diff --git a/src/profiling/symbolizer/filesystem_posix.cc b/src/profiling/symbolizer/filesystem_posix.cc
new file mode 100644
index 0000000..f66cbca
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_posix.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/filesystem.h"
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+#include <fts.h>
+#include <sys/stat.h>
+#endif
+
+#include <string>
+
+#include "perfetto/ext/base/file_utils.h"
+
+namespace perfetto {
+namespace profiling {
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
+  std::vector<char*> dir_cstrs;
+  for (std::string& dir : dirs)
+    dir_cstrs.emplace_back(&dir[0]);
+  dir_cstrs.push_back(nullptr);
+  base::ScopedResource<FTS*, fts_close, nullptr> fts(
+      fts_open(&dir_cstrs[0], FTS_LOGICAL | FTS_NOCHDIR, nullptr));
+  if (!fts) {
+    PERFETTO_PLOG("fts_open");
+    return false;
+  }
+  FTSENT* ent;
+  while ((ent = fts_read(*fts))) {
+    if (ent->fts_info & FTS_F)
+      fn(ent->fts_path, static_cast<size_t>(ent->fts_statp->st_size));
+  }
+  return true;
+}
+
+size_t GetFileSize(const std::string& file_path) {
+  base::ScopedFile fd(base::OpenFile(file_path, O_RDONLY | O_CLOEXEC));
+  if (!fd) {
+    PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+    return 0;
+  }
+  struct stat buf;
+  if (fstat(*fd, &buf) == -1) {
+    return 0;
+  }
+  return static_cast<size_t>(buf.st_size);
+}
+#else
+bool WalkDirectories(std::vector<std::string>, FileCallback) {
+  return false;
+}
+size_t GetFileSize(const std::string&) {
+  return 0;
+}
+#endif
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/filesystem_windows.cc b/src/profiling/symbolizer/filesystem_windows.cc
new file mode 100644
index 0000000..bb2652c
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_windows.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/filesystem.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include <Windows.h>
+
+namespace perfetto {
+namespace profiling {
+
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
+  std::vector<std::string> sub_dirs;
+  for (const std::string& dir : dirs) {
+    WIN32_FIND_DATAA file;
+    HANDLE fh = FindFirstFileA((dir + "\\*").c_str(), &file);
+    if (fh != INVALID_HANDLE_VALUE) {
+      do {
+        std::string file_path = dir + "\\" + file.cFileName;
+        if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+          if (strcmp(file.cFileName, ".") != 0 &&
+              strcmp(file.cFileName, "..") != 0) {
+            sub_dirs.push_back(file_path);
+          }
+        } else {
+          ULARGE_INTEGER size;
+          size.HighPart = file.nFileSizeHigh;
+          size.LowPart = file.nFileSizeLow;
+          fn(file_path.c_str(), size.QuadPart);
+        }
+      } while (FindNextFileA(fh, &file));
+    }
+    CloseHandle(fh);
+  }
+  if (!sub_dirs.empty()) {
+    WalkDirectories(sub_dirs, fn);
+  }
+  return true;
+}
+
+size_t GetFileSize(const std::string& file_path) {
+  HANDLE file =
+      CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+  if (file == INVALID_HANDLE_VALUE) {
+    PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+    return 0;
+  }
+  LARGE_INTEGER file_size;
+  file_size.QuadPart = 0;
+  if (!GetFileSizeEx(file, &file_size)) {
+    PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+  }
+  CloseHandle(file);
+  return static_cast<size_t>(file_size.QuadPart);
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index 18a7fd2..0a0ec4a 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2019 The Android Open Source Project
  *
@@ -20,18 +19,24 @@
 #include <fcntl.h>
 
 #include <memory>
+#include <sstream>
 #include <string>
 #include <vector>
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/symbolizer/filesystem.h"
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
 
 namespace perfetto {
 namespace profiling {
 
+// TODO(fmayer): Fix up name. This suggests it always returns a symbolizer or
+// dies, which isn't the case.
 std::unique_ptr<Symbolizer> LocalSymbolizerOrDie(
     std::vector<std::string> binary_path,
     const char* mode) {
@@ -61,49 +66,62 @@
 // Most of this translation unit is built only on Linux and MacOS. See
 // //gn/BUILD.gn.
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
 
-#include <fts.h>
 #include <inttypes.h>
 #include <signal.h>
-#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+constexpr const char* kDefaultSymbolizer = "llvm-symbolizer.exe";
+#else
+constexpr const char* kDefaultSymbolizer = "llvm-symbolizer";
+#endif
 
 namespace perfetto {
 namespace profiling {
 
-namespace {
-
-std::vector<std::string> GetLines(FILE* f) {
+std::vector<std::string> GetLines(
+    std::function<int64_t(char*, size_t)> fn_read) {
   std::vector<std::string> lines;
-  size_t n = 0;
-  char* line = nullptr;
-  ssize_t rd = 0;
-  do {
-    rd = getline(&line, &n, f);
-    // Do not read empty line that terminates the output.
-    if (rd > 1) {
-      // Remove newline character.
-      PERFETTO_DCHECK(line[rd - 1] == '\n');
-      line[rd - 1] = '\0';
-      lines.emplace_back(line);
+  char buffer[512];
+  int64_t rd = 0;
+  // Cache the partial line of the previous read.
+  std::string last_line;
+  while ((rd = fn_read(buffer, sizeof(buffer))) > 0) {
+    std::string data(buffer, static_cast<size_t>(rd));
+    // Create stream buffer of last partial line + new data
+    std::stringstream stream(last_line + data);
+    std::string line;
+    last_line = "";
+    while (std::getline(stream, line)) {
+      // Return from reading when we read an empty line.
+      if (line.empty()) {
+        return lines;
+      } else if (stream.eof()) {
+        // Cache off the partial line when we hit end of stream.
+        last_line += line;
+        break;
+      } else {
+        lines.push_back(line);
+      }
     }
-    free(line);
-    line = nullptr;
-    n = 0;
-  } while (rd > 1);
+  }
+  if (rd == -1) {
+    PERFETTO_ELOG("Failed to read data from subprocess.");
+  }
   return lines;
 }
 
+namespace {
 // We cannot just include elf.h, as that only exists on Linux, and we want to
 // allow symbolization on other platforms as well. As we only need a small
 // subset, it is easiest to define the constants and structs ourselves.
+constexpr auto PT_LOAD = 1;
+constexpr auto PF_X = 1;
 constexpr auto SHT_NOTE = 7;
 constexpr auto NT_GNU_BUILD_ID = 3;
 constexpr auto ELFCLASS32 = 1;
@@ -157,6 +175,16 @@
     Word n_descsz;
     Word n_type;
   };
+  struct Phdr {
+    uint32_t p_type;
+    Off p_offset;
+    Addr p_vaddr;
+    Addr p_paddr;
+    uint32_t p_filesz;
+    uint32_t p_memsz;
+    uint32_t p_flags;
+    uint32_t p_align;
+  };
 };
 
 struct Elf64 {
@@ -201,6 +229,16 @@
     Word n_descsz;
     Word n_type;
   };
+  struct Phdr {
+    uint32_t p_type;
+    uint32_t p_flags;
+    Off p_offset;
+    Addr p_vaddr;
+    Addr p_paddr;
+    uint64_t p_filesz;
+    uint64_t p_memsz;
+    uint64_t p_align;
+  };
 };
 
 template <typename E>
@@ -209,6 +247,12 @@
       static_cast<char*>(mem) + ehdr->e_shoff + i * sizeof(typename E::Shdr));
 }
 
+template <typename E>
+typename E::Phdr* GetPhdr(void* mem, const typename E::Ehdr* ehdr, size_t i) {
+  return reinterpret_cast<typename E::Phdr*>(
+      static_cast<char*>(mem) + ehdr->e_phoff + i * sizeof(typename E::Phdr));
+}
+
 bool InRange(const void* base,
              size_t total_size,
              const void* ptr,
@@ -218,6 +262,26 @@
 }
 
 template <typename E>
+base::Optional<uint64_t> GetLoadBias(void* mem, size_t size) {
+  const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
+  if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
+    PERFETTO_ELOG("Corrupted ELF.");
+    return base::nullopt;
+  }
+  for (size_t i = 0; i < ehdr->e_phnum; ++i) {
+    typename E::Phdr* phdr = GetPhdr<E>(mem, ehdr, i);
+    if (!InRange(mem, size, phdr, sizeof(typename E::Phdr))) {
+      PERFETTO_ELOG("Corrupted ELF.");
+      return base::nullopt;
+    }
+    if (phdr->p_type == PT_LOAD && phdr->p_flags & PF_X) {
+      return phdr->p_vaddr - phdr->p_offset;
+    }
+  }
+  return 0u;
+}
+
+template <typename E>
 base::Optional<std::string> GetBuildId(void* mem, size_t size) {
   const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
   if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
@@ -267,27 +331,6 @@
   return base::nullopt;
 }
 
-class ScopedMmap {
- public:
-  ScopedMmap(void* addr,
-             size_t length,
-             int prot,
-             int flags,
-             int fd,
-             off_t offset)
-      : length_(length), ptr_(mmap(addr, length, prot, flags, fd, offset)) {}
-  ~ScopedMmap() {
-    if (ptr_ != MAP_FAILED)
-      munmap(ptr_, length_);
-  }
-
-  void* operator*() { return ptr_; }
-
- private:
-  size_t length_;
-  void* ptr_;
-};
-
 std::string SplitBuildID(const std::string& hex_build_id) {
   if (hex_build_id.size() < 3) {
     PERFETTO_DFATAL_OR_ELOG("Invalid build-id (< 3 char) %s",
@@ -305,15 +348,18 @@
           mem[EI_MAG2] == ELFMAG2 && mem[EI_MAG3] == ELFMAG3);
 }
 
-base::Optional<std::string> GetBuildId(int fd, const struct stat* statbuf) {
-  size_t size = static_cast<size_t>(statbuf->st_size);
+struct BuildIdAndLoadBias {
+  std::string build_id;
+  uint64_t load_bias;
+};
 
+base::Optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(const char* fname,
+                                                         size_t size) {
   static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
   if (size <= EI_CLASS)
     return base::nullopt;
-
-  ScopedMmap map(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
-  if (*map == MAP_FAILED) {
+  ScopedReadMmap map(fname, size);
+  if (!map.IsValid()) {
     PERFETTO_PLOG("mmap");
     return base::nullopt;
   }
@@ -322,60 +368,55 @@
   if (!IsElf(mem, size))
     return base::nullopt;
 
+  base::Optional<std::string> build_id;
+  base::Optional<uint64_t> load_bias;
   switch (mem[EI_CLASS]) {
     case ELFCLASS32:
-      return GetBuildId<Elf32>(mem, size);
+      build_id = GetBuildId<Elf32>(mem, size);
+      load_bias = GetLoadBias<Elf32>(mem, size);
+      break;
     case ELFCLASS64:
-      return GetBuildId<Elf64>(mem, size);
+      build_id = GetBuildId<Elf64>(mem, size);
+      load_bias = GetLoadBias<Elf64>(mem, size);
+      break;
     default:
       return base::nullopt;
   }
+  if (build_id && load_bias) {
+    return BuildIdAndLoadBias{*build_id, *load_bias};
+  }
+  return base::nullopt;
 }
 
-template <typename F>
-bool WalkDirectories(std::vector<std::string> dirs, F fn) {
-  std::vector<char*> dir_cstrs;
-  for (std::string& dir : dirs)
-    dir_cstrs.emplace_back(&dir[0]);
-  dir_cstrs.push_back(nullptr);
-  base::ScopedResource<FTS*, fts_close, nullptr> fts(
-      fts_open(&dir_cstrs[0], FTS_LOGICAL | FTS_NOCHDIR, nullptr));
-  if (!fts) {
-    PERFETTO_PLOG("fts_open");
-    return false;
-  }
-  FTSENT* ent;
-  while ((ent = fts_read(*fts))) {
-    if (ent->fts_info & FTS_F)
-      fn(ent->fts_path, ent->fts_statp);
-  }
-  return true;
-}
-
-std::map<std::string, std::string> BuildIdIndex(std::vector<std::string> dirs) {
-  std::map<std::string, std::string> result;
-  WalkDirectories(
-      std::move(dirs), [&result](const char* fname, const struct stat* stat) {
-        char magic[EI_MAG3 + 1];
-        auto fd = base::OpenFile(fname, O_RDONLY | O_CLOEXEC);
-        ssize_t rd = PERFETTO_EINTR(read(*fd, &magic, sizeof(magic)));
-        if (rd == -1) {
-          PERFETTO_PLOG("Failed to read %s", fname);
-          return;
-        }
-        if (!IsElf(magic, static_cast<size_t>(rd))) {
-          PERFETTO_DLOG("%s not an ELF.", fname);
-          return;
-        }
-        if (lseek(*fd, 0, SEEK_SET) == -1) {
-          PERFETTO_PLOG("Failed to seek %s", fname);
-          return;
-        }
-        base::Optional<std::string> build_id = GetBuildId(*fd, stat);
-        if (build_id) {
-          result.emplace(*build_id, fname);
-        }
-      });
+std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
+  std::map<std::string, FoundBinary> result;
+  WalkDirectories(std::move(dirs), [&result](const char* fname, size_t size) {
+    char magic[EI_MAG3 + 1];
+    // Scope file access. On windows OpenFile opens an exclusive lock.
+    // This lock needs to be released before mapping the file.
+    {
+      base::ScopedFile fd(base::OpenFile(fname, O_RDONLY));
+      if (!fd) {
+        PERFETTO_PLOG("Failed to open %s", fname);
+        return;
+      }
+      ssize_t rd = base::Read(*fd, &magic, sizeof(magic));
+      if (rd != sizeof(magic)) {
+        PERFETTO_PLOG("Failed to read %s", fname);
+        return;
+      }
+      if (!IsElf(magic, static_cast<size_t>(rd))) {
+        PERFETTO_DLOG("%s not an ELF.", fname);
+        return;
+      }
+    }
+    base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
+        GetBuildIdAndLoadBias(fname, size);
+    if (build_id_and_load_bias) {
+      result.emplace(build_id_and_load_bias->build_id,
+                     FoundBinary{fname, build_id_and_load_bias->load_bias});
+    }
+  });
   return result;
 }
 
@@ -405,7 +446,7 @@
 LocalBinaryIndexer::LocalBinaryIndexer(std::vector<std::string> roots)
     : buildid_to_file_(BuildIdIndex(std::move(roots))) {}
 
-base::Optional<std::string> LocalBinaryIndexer::FindBinary(
+base::Optional<FoundBinary> LocalBinaryIndexer::FindBinary(
     const std::string& abspath,
     const std::string& build_id) {
   auto it = buildid_to_file_.find(build_id);
@@ -421,14 +462,14 @@
 LocalBinaryFinder::LocalBinaryFinder(std::vector<std::string> roots)
     : roots_(std::move(roots)) {}
 
-base::Optional<std::string> LocalBinaryFinder::FindBinary(
+base::Optional<FoundBinary> LocalBinaryFinder::FindBinary(
     const std::string& abspath,
     const std::string& build_id) {
   auto p = cache_.emplace(abspath, base::nullopt);
   if (!p.second)
     return p.first->second;
 
-  base::Optional<std::string>& cache_entry = p.first->second;
+  base::Optional<FoundBinary>& cache_entry = p.first->second;
 
   for (const std::string& root_str : roots_) {
     cache_entry = FindBinaryInRoot(root_str, abspath, build_id);
@@ -440,22 +481,30 @@
   return cache_entry;
 }
 
-bool LocalBinaryFinder::IsCorrectFile(const std::string& symbol_file,
-                                      const std::string& build_id) {
-  base::ScopedFile fd(base::OpenFile(symbol_file, O_RDONLY));
-  if (!fd)
-    return false;
-  struct stat statbuf;
-  if (fstat(*fd, &statbuf) == -1)
-    return false;
+base::Optional<FoundBinary> LocalBinaryFinder::IsCorrectFile(
+    const std::string& symbol_file,
+    const std::string& build_id) {
+  if (!base::FileExists(symbol_file)) {
+    return base::nullopt;
+  }
+  // Openfile opens the file with an exclusive lock on windows.
+  size_t size = GetFileSize(symbol_file);
 
-  base::Optional<std::string> file_build_id = GetBuildId(*fd, &statbuf);
-  if (!file_build_id)
-    return false;
-  return *file_build_id == build_id;
+  if (size == 0) {
+    return base::nullopt;
+  }
+
+  base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
+      GetBuildIdAndLoadBias(symbol_file.c_str(), size);
+  if (!build_id_and_load_bias)
+    return base::nullopt;
+  if (build_id_and_load_bias->build_id != build_id) {
+    return base::nullopt;
+  }
+  return FoundBinary{symbol_file, build_id_and_load_bias->load_bias};
 }
 
-base::Optional<std::string> LocalBinaryFinder::FindBinaryInRoot(
+base::Optional<FoundBinary> LocalBinaryFinder::FindBinaryInRoot(
     const std::string& root_str,
     const std::string& abspath,
     const std::string& build_id) {
@@ -491,29 +540,35 @@
   // * $ROOT/foo.so
   // * $ROOT/.build-id/ab/cd1234.debug
 
+  base::Optional<FoundBinary> result;
+
   std::string symbol_file = root_str + "/" + dirname + "/" + filename;
-  if (access(symbol_file.c_str(), F_OK) == 0 &&
-      IsCorrectFile(symbol_file, build_id))
-    return {symbol_file};
+  result = IsCorrectFile(symbol_file, build_id);
+  if (result) {
+    return result;
+  }
 
   if (filename.find(kApkPrefix) == 0) {
     symbol_file =
         root_str + "/" + dirname + "/" + filename.substr(sizeof(kApkPrefix));
-    if (access(symbol_file.c_str(), F_OK) == 0 &&
-        IsCorrectFile(symbol_file, build_id))
-      return {symbol_file};
+    result = IsCorrectFile(symbol_file, build_id);
+    if (result) {
+      return result;
+    }
   }
 
   symbol_file = root_str + "/" + filename;
-  if (access(symbol_file.c_str(), F_OK) == 0 &&
-      IsCorrectFile(symbol_file, build_id))
-    return {symbol_file};
+  result = IsCorrectFile(symbol_file, build_id);
+  if (result) {
+    return result;
+  }
 
   if (filename.find(kApkPrefix) == 0) {
     symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix));
-    if (access(symbol_file.c_str(), F_OK) == 0 &&
-        IsCorrectFile(symbol_file, build_id))
-      return {symbol_file};
+    result = IsCorrectFile(symbol_file, build_id);
+    if (result) {
+      return result;
+    }
   }
 
   std::string hex_build_id = base::ToHex(build_id.c_str(), build_id.size());
@@ -521,9 +576,10 @@
   if (!split_hex_build_id.empty()) {
     symbol_file =
         root_str + "/" + ".build-id" + "/" + split_hex_build_id + ".debug";
-    if (access(symbol_file.c_str(), F_OK) == 0 &&
-        IsCorrectFile(symbol_file, build_id))
-      return {symbol_file};
+    result = IsCorrectFile(symbol_file, build_id);
+    if (result) {
+      return result;
+    }
   }
 
   return base::nullopt;
@@ -531,50 +587,29 @@
 
 LocalBinaryFinder::~LocalBinaryFinder() = default;
 
-Subprocess::Subprocess(const std::string& file, std::vector<std::string> args)
-    : input_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)),
-      output_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)) {
-  std::vector<char*> c_str_args(args.size() + 1, nullptr);
-  for (std::string& arg : args)
-    c_str_args.push_back(&(arg[0]));
-
-  if ((pid_ = fork()) == 0) {
-    // Child
-    PERFETTO_CHECK(dup2(*input_pipe_.rd, STDIN_FILENO) != -1);
-    PERFETTO_CHECK(dup2(*output_pipe_.wr, STDOUT_FILENO) != -1);
-    input_pipe_.wr.reset();
-    output_pipe_.rd.reset();
-    if (execvp(file.c_str(), &(c_str_args[0])) == -1)
-      PERFETTO_FATAL("Failed to exec %s", file.c_str());
-  }
-  PERFETTO_CHECK(pid_ != -1);
-  input_pipe_.rd.reset();
-  output_pipe_.wr.reset();
+LLVMSymbolizerProcess::LLVMSymbolizerProcess(const std::string& symbolizer_path)
+    :
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+      subprocess_(symbolizer_path, {}) {
 }
-
-Subprocess::~Subprocess() {
-  if (pid_ != -1) {
-    kill(pid_, SIGKILL);
-    int wstatus;
-    PERFETTO_EINTR(waitpid(pid_, &wstatus, 0));
-  }
+#else
+      subprocess_(symbolizer_path, {"llvm-symbolizer"}) {
 }
-
-LLVMSymbolizerProcess::LLVMSymbolizerProcess()
-    : subprocess_("llvm-symbolizer", {"llvm-symbolizer"}),
-      read_file_(fdopen(subprocess_.read_fd(), "r")) {}
+#endif
 
 std::vector<SymbolizedFrame> LLVMSymbolizerProcess::Symbolize(
     const std::string& binary,
     uint64_t address) {
   std::vector<SymbolizedFrame> result;
-
-  if (PERFETTO_EINTR(dprintf(subprocess_.write_fd(), "%s 0x%" PRIx64 "\n",
-                             binary.c_str(), address)) < 0) {
+  char buffer[1024];
+  int size = sprintf(buffer, "\"%s\" 0x%" PRIx64 "\n", binary.c_str(), address);
+  if (subprocess_.Write(buffer, static_cast<size_t>(size)) < 0) {
     PERFETTO_ELOG("Failed to write to llvm-symbolizer.");
     return result;
   }
-  auto lines = GetLines(read_file_);
+  auto lines = GetLines([&](char* read_buffer, size_t buffer_size) {
+    return subprocess_.Read(read_buffer, buffer_size);
+  });
   // llvm-symbolizer writes out records in the form of
   // Foo(Bar*)
   // foo.cc:123
@@ -606,18 +641,37 @@
 std::vector<std::vector<SymbolizedFrame>> LocalSymbolizer::Symbolize(
     const std::string& mapping_name,
     const std::string& build_id,
+    uint64_t load_bias,
     const std::vector<uint64_t>& addresses) {
-  base::Optional<std::string> binary =
+  base::Optional<FoundBinary> binary =
       finder_->FindBinary(mapping_name, build_id);
   if (!binary)
     return {};
+  uint64_t load_bias_correction = 0;
+  if (binary->load_bias > load_bias) {
+    // On Android 10, there was a bug in libunwindstack that would incorrectly
+    // calculate the load_bias, and thus the relative PC. This would end up in
+    // frames that made no sense. We can fix this up after the fact if we
+    // detect this situation.
+    load_bias_correction = binary->load_bias - load_bias;
+    PERFETTO_LOG("Correcting load bias by %" PRIu64 " for %s",
+                 load_bias_correction, mapping_name.c_str());
+  }
   std::vector<std::vector<SymbolizedFrame>> result;
   result.reserve(addresses.size());
   for (uint64_t address : addresses)
-    result.emplace_back(llvm_symbolizer_.Symbolize(*binary, address));
+    result.emplace_back(llvm_symbolizer_.Symbolize(
+        binary->file_name, address + load_bias_correction));
   return result;
 }
 
+LocalSymbolizer::LocalSymbolizer(const std::string& symbolizer_path,
+                                 std::unique_ptr<BinaryFinder> finder)
+    : llvm_symbolizer_(symbolizer_path), finder_(std::move(finder)) {}
+
+LocalSymbolizer::LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)
+    : LocalSymbolizer(kDefaultSymbolizer, std::move(finder)) {}
+
 LocalSymbolizer::~LocalSymbolizer() = default;
 
 }  // namespace profiling
diff --git a/src/profiling/symbolizer/local_symbolizer.h b/src/profiling/symbolizer/local_symbolizer.h
index df017ce..01d1fe4 100644
--- a/src/profiling/symbolizer/local_symbolizer.h
+++ b/src/profiling/symbolizer/local_symbolizer.h
@@ -23,22 +23,28 @@
 #include <vector>
 
 #include "perfetto/ext/base/optional.h"
-#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/symbolizer/subprocess.h"
 #include "src/profiling/symbolizer/symbolizer.h"
 
 namespace perfetto {
 namespace profiling {
 
-#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
 bool ParseLlvmSymbolizerLine(const std::string& line,
                              std::string* file_name,
                              uint32_t* line_no);
+std::vector<std::string> GetLines(
+    std::function<int64_t(char*, size_t)> fn_read);
+
+struct FoundBinary {
+  std::string file_name;
+  uint64_t load_bias;
+};
 
 class BinaryFinder {
  public:
   virtual ~BinaryFinder();
-  virtual base::Optional<std::string> FindBinary(
+  virtual base::Optional<FoundBinary> FindBinary(
       const std::string& abspath,
       const std::string& build_id) = 0;
 };
@@ -47,72 +53,58 @@
  public:
   explicit LocalBinaryIndexer(std::vector<std::string> roots);
 
-  base::Optional<std::string> FindBinary(const std::string& abspath,
+  base::Optional<FoundBinary> FindBinary(const std::string& abspath,
                                          const std::string& build_id) override;
   ~LocalBinaryIndexer() override;
 
  private:
-  std::map<std::string, std::string> buildid_to_file_;
+  std::map<std::string, FoundBinary> buildid_to_file_;
 };
 
 class LocalBinaryFinder : public BinaryFinder {
  public:
   explicit LocalBinaryFinder(std::vector<std::string> roots);
 
-  base::Optional<std::string> FindBinary(const std::string& abspath,
+  base::Optional<FoundBinary> FindBinary(const std::string& abspath,
                                          const std::string& build_id) override;
 
   ~LocalBinaryFinder() override;
 
  private:
-  bool IsCorrectFile(const std::string& symbol_file,
-                     const std::string& build_id);
+  base::Optional<FoundBinary> IsCorrectFile(const std::string& symbol_file,
+                                            const std::string& build_id);
 
-  base::Optional<std::string> FindBinaryInRoot(const std::string& root_str,
+  base::Optional<FoundBinary> FindBinaryInRoot(const std::string& root_str,
                                                const std::string& abspath,
                                                const std::string& build_id);
 
  private:
   std::vector<std::string> roots_;
-  std::map<std::string, base::Optional<std::string>> cache_;
-};
-
-class Subprocess {
- public:
-  Subprocess(const std::string& file, std::vector<std::string> args);
-
-  ~Subprocess();
-
-  int read_fd() { return output_pipe_.rd.get(); }
-  int write_fd() { return input_pipe_.wr.get(); }
-
- private:
-  base::Pipe input_pipe_;
-  base::Pipe output_pipe_;
-
-  pid_t pid_ = -1;
+  std::map<std::string, base::Optional<FoundBinary>> cache_;
 };
 
 class LLVMSymbolizerProcess {
  public:
-  LLVMSymbolizerProcess();
+  explicit LLVMSymbolizerProcess(const std::string& symbolizer_path);
 
   std::vector<SymbolizedFrame> Symbolize(const std::string& binary,
                                          uint64_t address);
 
  private:
   Subprocess subprocess_;
-  FILE* read_file_;
 };
 
 class LocalSymbolizer : public Symbolizer {
  public:
-  explicit LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)
-      : finder_(std::move(finder)) {}
+  LocalSymbolizer(const std::string& symbolizer_path,
+                  std::unique_ptr<BinaryFinder> finder);
+
+  explicit LocalSymbolizer(std::unique_ptr<BinaryFinder> finder);
 
   std::vector<std::vector<SymbolizedFrame>> Symbolize(
       const std::string& mapping_name,
       const std::string& build_id,
+      uint64_t load_bias,
       const std::vector<uint64_t>& address) override;
 
   ~LocalSymbolizer() override;
@@ -122,8 +114,6 @@
   std::unique_ptr<BinaryFinder> finder_;
 };
 
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
 std::unique_ptr<Symbolizer> LocalSymbolizerOrDie(
     std::vector<std::string> binary_path,
     const char* mode);
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
index b515866..b96d318 100644
--- a/src/profiling/symbolizer/local_symbolizer_unittest.cc
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -20,12 +20,29 @@
 // This translation unit is built only on Linux and MacOS. See //gn/BUILD.gn.
 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
 
+#include "src/base/test/utils.h"
 #include "src/profiling/symbolizer/local_symbolizer.h"
+#include "src/profiling/symbolizer/subprocess.h"
 
 namespace perfetto {
 namespace profiling {
 namespace {
 
+void RunAndValidateParseLines(std::string raw_contents) {
+  std::istringstream stream(raw_contents);
+  auto read_callback = [&stream](char* buffer, size_t size) {
+    stream.get(buffer, static_cast<int>(size), '\0');
+    return strlen(buffer);
+  };
+  std::vector<std::string> lines = GetLines(read_callback);
+  std::istringstream validation(raw_contents);
+  for (const std::string& actual : lines) {
+    std::string expected;
+    getline(validation, expected);
+    EXPECT_EQ(actual, expected);
+  }
+}
+
 TEST(LocalSymbolizerTest, ParseLineWindows) {
   std::string file_name;
   uint32_t lineno;
@@ -35,6 +52,49 @@
   EXPECT_EQ(lineno, 123u);
 }
 
+TEST(LocalSymbolizerTest, ParseLinesExpectedOutput) {
+  std::string raw_contents =
+      "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
+      "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
+      "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
+      "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
+      "FSlateRenderingParams const&)\n"
+      "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
+      "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
+  RunAndValidateParseLines(raw_contents);
+}
+
+TEST(LocalSymbolizerTest, ParseLinesErrorOutput) {
+  std::string raw_contents =
+      "LLVMSymbolizer: error reading file: No such file or directory\n"
+      "??\n"
+      "??:0:0\n";
+  RunAndValidateParseLines(raw_contents);
+}
+
+TEST(LocalSymbolizerTest, ParseLinesSingleCharRead) {
+  std::string raw_contents =
+      "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
+      "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
+      "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
+      "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
+      "FSlateRenderingParams const&)\n"
+      "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
+      "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
+  std::istringstream stream(raw_contents);
+  auto read_callback = [&stream](char* buffer, size_t) {
+    stream.get(buffer, 1, '\0');
+    return strlen(buffer);
+  };
+  std::vector<std::string> lines = GetLines(read_callback);
+  std::istringstream validation(raw_contents);
+  for (const std::string& actual : lines) {
+    std::string expected;
+    getline(validation, expected);
+    EXPECT_EQ(actual, expected);
+  }
+}
+
 }  // namespace
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/symbolizer/scoped_read_mmap.h b/src/profiling/symbolizer/scoped_read_mmap.h
new file mode 100644
index 0000000..69a028a
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap.h
@@ -0,0 +1,48 @@
+/*
+ * 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_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
+#define SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
+
+#include "perfetto/ext/base/scoped_file.h"
+
+namespace perfetto {
+namespace profiling {
+
+class ScopedReadMmap {
+ public:
+  ScopedReadMmap(const char* fname, size_t length);
+  virtual ~ScopedReadMmap();
+
+  void* operator*() { return ptr_; }
+
+  bool IsValid();
+
+ private:
+  size_t length_;
+  void* ptr_;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  void* file_ = nullptr;
+  void* map_ = nullptr;
+#else
+  base::ScopedFile fd_;
+#endif
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
diff --git a/src/profiling/symbolizer/scoped_read_mmap_posix.cc b/src/profiling/symbolizer/scoped_read_mmap_posix.cc
new file mode 100644
index 0000000..f6c3276
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap_posix.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+
+#include <sys/mman.h>
+
+namespace perfetto {
+namespace profiling {
+
+ScopedReadMmap::ScopedReadMmap(const char* fname, size_t length)
+    : length_(length), fd_(base::OpenFile(fname, O_RDONLY)) {
+  if (!fd_) {
+    PERFETTO_PLOG("Failed to open %s", fname);
+    return;
+  }
+  ptr_ = mmap(nullptr, length, PROT_READ, MAP_PRIVATE, *fd_, 0);
+}
+
+ScopedReadMmap::~ScopedReadMmap() {
+  if (ptr_ != MAP_FAILED)
+    munmap(ptr_, length_);
+}
+
+bool ScopedReadMmap::IsValid() {
+  return ptr_ != MAP_FAILED;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/scoped_read_mmap_windows.cc b/src/profiling/symbolizer/scoped_read_mmap_windows.cc
new file mode 100644
index 0000000..d56d913
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap_windows.cc
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include <Windows.h>
+
+namespace perfetto {
+namespace profiling {
+
+ScopedReadMmap::ScopedReadMmap(const char* fName, size_t length)
+    : length_(length), ptr_(nullptr) {
+  file_ = CreateFileA(fName, GENERIC_READ, FILE_SHARE_READ, nullptr,
+                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+  if (file_ == INVALID_HANDLE_VALUE) {
+    PERFETTO_DLOG("Failed to open file: %s", fName);
+    return;
+  }
+  map_ = CreateFileMapping(file_, nullptr, PAGE_READONLY, 0, 0, nullptr);
+  if (map_ == INVALID_HANDLE_VALUE) {
+    PERFETTO_DLOG("Failed to mmap file");
+    return;
+  }
+  ptr_ = MapViewOfFile(map_, FILE_MAP_READ, 0, 0, length_);
+  if (ptr_ == nullptr) {
+    PERFETTO_DLOG("Failed to map view of file");
+  }
+}
+
+ScopedReadMmap::~ScopedReadMmap() {
+  if (ptr_ != nullptr) {
+    UnmapViewOfFile(ptr_);
+  }
+  if (map_ != nullptr && map_ != INVALID_HANDLE_VALUE) {
+    CloseHandle(map_);
+  }
+  if (file_ != nullptr && file_ != INVALID_HANDLE_VALUE) {
+    CloseHandle(file_);
+  }
+}
+
+bool ScopedReadMmap::IsValid() {
+  return ptr_ != nullptr;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/subprocess.h b/src/profiling/symbolizer/subprocess.h
new file mode 100644
index 0000000..09f0bac
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess.h
@@ -0,0 +1,60 @@
+
+/*
+ * 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_PROFILING_SYMBOLIZER_SUBPROCESS_H_
+#define SRC_PROFILING_SYMBOLIZER_SUBPROCESS_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <sys/types.h>
+#include <unistd.h>
+#include "perfetto/ext/base/pipe.h"
+#endif
+
+namespace perfetto {
+namespace profiling {
+
+class Subprocess {
+ public:
+  Subprocess(const std::string& file, std::vector<std::string> args);
+  ~Subprocess();
+
+  int64_t Write(const char* buffer, size_t size);
+  int64_t Read(char* buffer, size_t size);
+
+ private:
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  void* child_pipe_in_read_ = nullptr;
+  void* child_pipe_in_write_ = nullptr;
+  void* child_pipe_out_read_ = nullptr;
+  void* child_pipe_out_write_ = nullptr;
+#else
+  base::Pipe input_pipe_;
+  base::Pipe output_pipe_;
+
+  pid_t pid_ = -1;
+#endif
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_SYMBOLIZER_SUBPROCESS_H_
diff --git a/src/profiling/symbolizer/subprocess_posix.cc b/src/profiling/symbolizer/subprocess_posix.cc
new file mode 100644
index 0000000..26408a8
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess_posix.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/subprocess.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/utils.h"
+
+namespace perfetto {
+namespace profiling {
+
+Subprocess::Subprocess(const std::string& file, std::vector<std::string> args)
+    : input_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)),
+      output_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)) {
+  std::vector<char*> c_str_args;
+  for (std::string& arg : args)
+    c_str_args.push_back(&(arg[0]));
+  c_str_args.push_back(nullptr);
+
+  if ((pid_ = fork()) == 0) {
+    // Child
+    PERFETTO_CHECK(dup2(*input_pipe_.rd, STDIN_FILENO) != -1);
+    PERFETTO_CHECK(dup2(*output_pipe_.wr, STDOUT_FILENO) != -1);
+    input_pipe_.wr.reset();
+    output_pipe_.rd.reset();
+    if (execvp(file.c_str(), c_str_args.data()) == -1)
+      PERFETTO_FATAL("Failed to exec %s", file.c_str());
+  }
+  PERFETTO_CHECK(pid_ != -1);
+  input_pipe_.rd.reset();
+  output_pipe_.wr.reset();
+}
+
+Subprocess::~Subprocess() {
+  if (pid_ != -1) {
+    kill(pid_, SIGKILL);
+    int wstatus;
+    PERFETTO_EINTR(waitpid(pid_, &wstatus, 0));
+  }
+}
+
+int64_t Subprocess::Write(const char* buffer, size_t size) {
+  if (!input_pipe_.wr) {
+    return -1;
+  }
+  return PERFETTO_EINTR(write(input_pipe_.wr.get(), buffer, size));
+}
+
+int64_t Subprocess::Read(char* buffer, size_t size) {
+  if (!output_pipe_.rd) {
+    return -1;
+  }
+  return PERFETTO_EINTR(read(output_pipe_.rd.get(), buffer, size));
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/subprocess_windows.cc b/src/profiling/symbolizer/subprocess_windows.cc
new file mode 100644
index 0000000..7f28ea4
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess_windows.cc
@@ -0,0 +1,133 @@
+
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/subprocess.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+
+#include <sstream>
+#include <string>
+
+#include <Windows.h>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace profiling {
+
+Subprocess::Subprocess(const std::string& file, std::vector<std::string> args) {
+  std::stringstream cmd;
+  cmd << file;
+  for (auto arg : args) {
+    cmd << " " << arg;
+  }
+  SECURITY_ATTRIBUTES attr;
+  attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  attr.bInheritHandle = true;
+  attr.lpSecurityDescriptor = nullptr;
+  // Create a pipe for the child process's STDOUT.
+  if (!CreatePipe(&child_pipe_out_read_, &child_pipe_out_write_, &attr, 0) ||
+      !SetHandleInformation(child_pipe_out_read_, HANDLE_FLAG_INHERIT, 0)) {
+    PERFETTO_ELOG("Failed to create stdout pipe");
+    return;
+  }
+  if (!CreatePipe(&child_pipe_in_read_, &child_pipe_in_write_, &attr, 0) ||
+      !SetHandleInformation(child_pipe_in_write_, HANDLE_FLAG_INHERIT, 0)) {
+    PERFETTO_ELOG("Failed to create stdin pipe");
+    return;
+  }
+
+  PROCESS_INFORMATION proc_info;
+  STARTUPINFOA start_info;
+  bool success = false;
+  // Set up members of the PROCESS_INFORMATION structure.
+  ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
+
+  // Set up members of the STARTUPINFO structure.
+  // This structure specifies the STDIN and STDOUT handles for redirection.
+  ZeroMemory(&start_info, sizeof(STARTUPINFOA));
+  start_info.cb = sizeof(STARTUPINFOA);
+  start_info.hStdError = child_pipe_out_write_;
+  start_info.hStdOutput = child_pipe_out_write_;
+  start_info.hStdInput = child_pipe_in_read_;
+  start_info.dwFlags |= STARTF_USESTDHANDLES;
+
+  // Create the child process.
+  success = CreateProcessA(nullptr,
+                           &(cmd.str()[0]),  // command line
+                           nullptr,          // process security attributes
+                           nullptr,      // primary thread security attributes
+                           TRUE,         // handles are inherited
+                           0,            // creation flags
+                           nullptr,      // use parent's environment
+                           nullptr,      // use parent's current directory
+                           &start_info,  // STARTUPINFO pointer
+                           &proc_info);  // receives PROCESS_INFORMATION
+
+  // If an error occurs, exit the application.
+  if (success) {
+    CloseHandle(proc_info.hProcess);
+    CloseHandle(proc_info.hThread);
+
+    // Close handles to the stdin and stdout pipes no longer needed by the child
+    // process. If they are not explicitly closed, there is no way to recognize
+    // that the child process has ended.
+
+    CloseHandle(child_pipe_out_write_);
+    CloseHandle(child_pipe_in_read_);
+  } else {
+    PERFETTO_ELOG("Failed to launch: %s", cmd.str().c_str());
+    child_pipe_in_read_ = nullptr;
+    child_pipe_in_write_ = nullptr;
+    child_pipe_out_write_ = nullptr;
+    child_pipe_out_read_ = nullptr;
+  }
+}
+
+Subprocess::~Subprocess() {
+  CloseHandle(child_pipe_out_read_);
+  CloseHandle(child_pipe_in_write_);
+}
+
+int64_t Subprocess::Write(const char* buffer, size_t size) {
+  if (child_pipe_in_write_ == nullptr) {
+    return -1;
+  }
+  DWORD bytes_written;
+  if (WriteFile(child_pipe_in_write_, buffer, static_cast<DWORD>(size),
+                &bytes_written, nullptr)) {
+    return static_cast<int64_t>(bytes_written);
+  }
+  return -1;
+}
+
+int64_t Subprocess::Read(char* buffer, size_t size) {
+  if (child_pipe_out_read_ == nullptr) {
+    return -1;
+  }
+  DWORD bytes_read;
+  if (ReadFile(child_pipe_out_read_, buffer, static_cast<DWORD>(size),
+               &bytes_read, nullptr)) {
+    return static_cast<int64_t>(bytes_read);
+  }
+  return -1;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/symbolize_database.cc b/src/profiling/symbolizer/symbolize_database.cc
index a608bc0..a002aea 100644
--- a/src/profiling/symbolizer/symbolize_database.cc
+++ b/src/profiling/symbolizer/symbolize_database.cc
@@ -21,8 +21,7 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_splitter.h"
-
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
@@ -37,7 +36,7 @@
 using trace_processor::Iterator;
 
 constexpr const char* kQueryUnsymbolized =
-    "select spm.name, spm.build_id, spf.rel_pc "
+    "select spm.name, spm.build_id, spf.rel_pc, spm.load_bias "
     "from stack_profile_frame spf "
     "join stack_profile_mapping spm "
     "on spf.mapping = spm.id "
@@ -45,6 +44,16 @@
 
 using NameAndBuildIdPair = std::pair<std::string, std::string>;
 
+struct UnsymbolizedMapping {
+  std::string name;
+  std::string build_id;
+  uint64_t load_bias;
+  bool operator<(const UnsymbolizedMapping& o) const {
+    return std::tie(name, build_id, load_bias) <
+           std::tie(o.name, o.build_id, o.load_bias);
+  }
+};
+
 std::string FromHex(const char* str, size_t size) {
   if (size % 2) {
     PERFETTO_DFATAL_OR_ELOG("Failed to parse hex %s", str);
@@ -71,15 +80,18 @@
   return FromHex(str.c_str(), str.size());
 }
 
-std::map<NameAndBuildIdPair, std::vector<uint64_t>> GetUnsymbolizedFrames(
+std::map<UnsymbolizedMapping, std::vector<uint64_t>> GetUnsymbolizedFrames(
     trace_processor::TraceProcessor* tp) {
-  std::map<std::pair<std::string, std::string>, std::vector<uint64_t>> res;
+  std::map<UnsymbolizedMapping, std::vector<uint64_t>> res;
   Iterator it = tp->ExecuteQuery(kQueryUnsymbolized);
   while (it.Next()) {
-    auto name_and_buildid =
-        std::make_pair(it.Get(0).AsString(), FromHex(it.Get(1).AsString()));
+    int64_t load_bias = it.Get(3).AsLong();
+    PERFETTO_CHECK(load_bias >= 0);
+    UnsymbolizedMapping unsymbolized_mapping{it.Get(0).AsString(),
+                                             FromHex(it.Get(1).AsString()),
+                                             static_cast<uint64_t>(load_bias)};
     int64_t rel_pc = it.Get(2).AsLong();
-    res[name_and_buildid].emplace_back(rel_pc);
+    res[unsymbolized_mapping].emplace_back(rel_pc);
   }
   if (!it.Status().ok()) {
     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
@@ -96,18 +108,19 @@
   PERFETTO_CHECK(symbolizer);
   auto unsymbolized = GetUnsymbolizedFrames(tp);
   for (auto it = unsymbolized.cbegin(); it != unsymbolized.cend(); ++it) {
-    const auto& name_and_buildid = it->first;
+    const auto& unsymbolized_mapping = it->first;
     const std::vector<uint64_t>& rel_pcs = it->second;
-    auto res = symbolizer->Symbolize(name_and_buildid.first,
-                                     name_and_buildid.second, rel_pcs);
+    auto res = symbolizer->Symbolize(unsymbolized_mapping.name,
+                                     unsymbolized_mapping.build_id,
+                                     unsymbolized_mapping.load_bias, rel_pcs);
     if (res.empty())
       continue;
 
     protozero::HeapBuffered<perfetto::protos::pbzero::Trace> trace;
     auto* packet = trace->add_packet();
     auto* module_symbols = packet->set_module_symbols();
-    module_symbols->set_path(name_and_buildid.first);
-    module_symbols->set_build_id(name_and_buildid.second);
+    module_symbols->set_path(unsymbolized_mapping.name);
+    module_symbols->set_build_id(unsymbolized_mapping.build_id);
     PERFETTO_DCHECK(res.size() == rel_pcs.size());
     for (size_t i = 0; i < res.size(); ++i) {
       auto* address_symbols = module_symbols->add_address_symbols();
@@ -124,13 +137,10 @@
 }
 
 std::vector<std::string> GetPerfettoBinaryPath() {
-  std::vector<std::string> roots;
   const char* root = getenv("PERFETTO_BINARY_PATH");
-  if (root != nullptr) {
-    for (base::StringSplitter sp(std::string(root), ':'); sp.Next();)
-      roots.emplace_back(sp.cur_token(), sp.cur_token_size());
-  }
-  return roots;
+  if (root != nullptr)
+    return base::SplitString(root, ":");
+  return {};
 }
 
 }  // namespace profiling
diff --git a/src/profiling/symbolizer/symbolizer.h b/src/profiling/symbolizer/symbolizer.h
index ff4ee50..3e96142 100644
--- a/src/profiling/symbolizer/symbolizer.h
+++ b/src/profiling/symbolizer/symbolizer.h
@@ -40,6 +40,7 @@
   virtual std::vector<std::vector<SymbolizedFrame>> Symbolize(
       const std::string& mapping_name,
       const std::string& build_id,
+      uint64_t load_bias,
       const std::vector<uint64_t>& address) = 0;
   virtual ~Symbolizer();
 };
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index de6c11c..4393153 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -14,10 +14,11 @@
 
 import("../../gn/fuzzer.gni")
 import("../../gn/perfetto.gni")
+import("../../gn/perfetto_component.gni")
 import("../../gn/proto_library.gni")
 import("../../gn/test.gni")
 
-source_set("protozero") {
+perfetto_component("protozero") {
   public_configs = [ "../../gn:default_config" ]
   public_deps = [
     "../../include/perfetto/base",
@@ -30,6 +31,7 @@
   sources = [
     "field.cc",
     "message.cc",
+    "message_arena.cc",
     "message_handle.cc",
     "packed_repeated_fields.cc",
     "proto_decoder.cc",
@@ -41,11 +43,6 @@
   ]
 }
 
-static_library("libprotozero") {
-  complete_static_lib = true
-  deps = [ ":protozero" ]
-}
-
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
diff --git a/src/protozero/message.cc b/src/protozero/message.cc
index 419f7f1..cfc9b37 100644
--- a/src/protozero/message.cc
+++ b/src/protozero/message.cc
@@ -20,6 +20,7 @@
 #include <type_traits>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/protozero/message_arena.h"
 #include "perfetto/protozero/message_handle.h"
 
 #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
@@ -38,14 +39,11 @@
 
 }  // namespace
 
-// static
-constexpr uint32_t Message::kMaxNestingDepth;
-
 // Do NOT put any code in the constructor or use default initialization.
-// Use the Reset() method below instead. See the header for the reason why.
+// Use the Reset() method below instead.
 
 // This method is called to initialize both root and nested messages.
-void Message::Reset(ScatteredStreamWriter* stream_writer) {
+void Message::Reset(ScatteredStreamWriter* stream_writer, MessageArena* arena) {
 // Older versions of libstdcxx don't have is_trivially_constructible.
 #if !defined(__GLIBCXX__) || __GLIBCXX__ >= 20170516
   static_assert(std::is_trivially_constructible<Message>::value,
@@ -54,19 +52,12 @@
 
   static_assert(std::is_trivially_destructible<Message>::value,
                 "Message must be trivially destructible");
-
-  static_assert(
-      sizeof(Message::nested_messages_arena_) >=
-          kMaxNestingDepth *
-              (sizeof(Message) - sizeof(Message::nested_messages_arena_)),
-      "Message::nested_messages_arena_ is too small");
-
   stream_writer_ = stream_writer;
+  arena_ = arena;
   size_ = 0;
   size_field_ = nullptr;
   size_already_written_ = 0;
   nested_message_ = nullptr;
-  nesting_depth_ = 0;
   finalized_ = false;
 #if PERFETTO_DCHECK_IS_ON()
   handle_ = nullptr;
@@ -147,7 +138,7 @@
   return size_;
 }
 
-void Message::BeginNestedMessageInternal(uint32_t field_id, Message* message) {
+Message* Message::BeginNestedMessageInternal(uint32_t field_id) {
   if (nested_message_)
     EndNestedMessage();
 
@@ -157,20 +148,22 @@
       proto_utils::MakeTagLengthDelimited(field_id), data);
   WriteToStream(data, data_end);
 
-  message->Reset(stream_writer_);
-  PERFETTO_CHECK(nesting_depth_ < kMaxNestingDepth);
-  message->nesting_depth_ = nesting_depth_ + 1;
+  Message* message = arena_->NewMessage();
+  message->Reset(stream_writer_, arena_);
 
   // The length of the nested message cannot be known upfront. So right now
   // just reserve the bytes to encode the size after the nested message is done.
   message->set_size_field(
       stream_writer_->ReserveBytes(proto_utils::kMessageLengthFieldSize));
   size_ += proto_utils::kMessageLengthFieldSize;
+
   nested_message_ = message;
+  return message;
 }
 
 void Message::EndNestedMessage() {
   size_ += nested_message_->Finalize();
+  arena_->DeleteLastMessage(nested_message_);
   nested_message_ = nullptr;
 }
 
diff --git a/src/protozero/message_arena.cc b/src/protozero/message_arena.cc
new file mode 100644
index 0000000..6e92cd0
--- /dev/null
+++ b/src/protozero/message_arena.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/protozero/message_arena.h"
+
+#include <atomic>
+#include <type_traits>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/protozero/message_handle.h"
+
+namespace protozero {
+
+MessageArena::MessageArena() {
+  // The code below assumes that there is always at least one block.
+  blocks_.emplace_front();
+  static_assert(std::alignment_of<decltype(blocks_.back().storage[0])>::value >=
+                    alignof(Message),
+                "MessageArea's storage is not properly aligned");
+}
+
+MessageArena::~MessageArena() = default;
+
+Message* MessageArena::NewMessage() {
+  PERFETTO_DCHECK(!blocks_.empty());  // Should never become empty.
+
+  Block* block = &blocks_.back();
+  if (PERFETTO_UNLIKELY(block->entries >= Block::kCapacity)) {
+    blocks_.emplace_back();
+    block = &blocks_.back();
+  }
+  const auto idx = block->entries++;
+  void* storage = &block->storage[idx];
+  PERFETTO_ASAN_UNPOISON(storage, sizeof(Message));
+  return new (storage) Message();
+}
+
+void MessageArena::DeleteLastMessageInternal() {
+  PERFETTO_DCHECK(!blocks_.empty());  // Should never be empty, see below.
+  Block* block = &blocks_.back();
+  PERFETTO_DCHECK(block->entries > 0);
+
+  // This is the reason why there is no ~Message() call here.
+  // MessageArea::Reset() (see header) also relies on dtor being trivial.
+  static_assert(std::is_trivially_destructible<Message>::value,
+                "Message must be trivially destructible");
+
+  --block->entries;
+  PERFETTO_ASAN_POISON(&block->storage[block->entries], sizeof(Message));
+
+  // Don't remove the first block to avoid malloc/free calls when the root
+  // message is reset. Hitting the allocator all the times is a waste of time.
+  if (block->entries == 0 && blocks_.size() > 1) {
+    blocks_.pop_back();
+  }
+}
+
+}  // namespace protozero
diff --git a/src/protozero/message_handle_unittest.cc b/src/protozero/message_handle_unittest.cc
index 7f0ee08..96c8600 100644
--- a/src/protozero/message_handle_unittest.cc
+++ b/src/protozero/message_handle_unittest.cc
@@ -16,7 +16,7 @@
 
 #include "perfetto/protozero/message_handle.h"
 
-#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/root_message.h"
 #include "test/gtest_and_gmock.h"
 
 namespace protozero {
@@ -24,8 +24,7 @@
 namespace {
 
 TEST(MessageHandleTest, MoveHandleSharedMessageDoesntFinalize) {
-  Message message;
-  message.Reset(nullptr);
+  RootMessage<Message> message;
 
   MessageHandle<Message> handle_1(&message);
   handle_1 = MessageHandle<Message>(&message);
diff --git a/src/protozero/message_unittest.cc b/src/protozero/message_unittest.cc
index d75dcd0..a061ad9 100644
--- a/src/protozero/message_unittest.cc
+++ b/src/protozero/message_unittest.cc
@@ -15,7 +15,6 @@
  */
 
 #include "perfetto/protozero/message.h"
-#include "perfetto/protozero/message_handle.h"
 
 #include <limits>
 #include <memory>
@@ -23,6 +22,8 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/root_message.h"
 #include "src/base/test/utils.h"
 #include "src/protozero/test/fake_scattered_buffer.h"
 #include "test/gtest_and_gmock.h"
@@ -38,7 +39,7 @@
 constexpr const char kEndWatermark[] = {'9', '8', '7', '6',
                                         'z', 'w', 'y', '\0'};
 
-class FakeRootMessage : public Message {};
+class FakeRootMessage : public RootMessage<Message> {};
 class FakeChildMessage : public Message {};
 
 uint32_t SimpleHash(const std::string& str) {
@@ -63,7 +64,10 @@
       EXPECT_STREQ(kStartWatermark, reinterpret_cast<char*>(mem.get()));
       EXPECT_STREQ(kEndWatermark,
                    reinterpret_cast<char*>(mem.get() + sizeof(kStartWatermark) +
-                                           sizeof(Message)));
+                                           sizeof(FakeRootMessage)));
+      FakeRootMessage* msg = reinterpret_cast<FakeRootMessage*>(
+          mem.get() + sizeof(kStartWatermark));
+      msg->~FakeRootMessage();
       mem.reset();
     }
     messages_.clear();
@@ -83,7 +87,7 @@
     memcpy(msg_start + sizeof(FakeRootMessage), kEndWatermark,
            sizeof(kEndWatermark));
     messages_.push_back(std::move(mem));
-    FakeRootMessage* msg = reinterpret_cast<FakeRootMessage*>(msg_start);
+    FakeRootMessage* msg = new (msg_start) FakeRootMessage();
     msg->Reset(stream_writer_.get());
     return msg;
   }
@@ -101,14 +105,16 @@
     return buffer_->GetBytesAsString(old_readback_pos, num_bytes);
   }
 
-  static void BuildNestedMessages(Message* msg, uint32_t depth = 0) {
+  static void BuildNestedMessages(Message* msg,
+                                  uint32_t max_depth,
+                                  uint32_t depth = 0) {
     for (uint32_t i = 1; i <= 128; ++i)
       msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes));
 
-    if (depth < Message::kMaxNestingDepth) {
+    if (depth < max_depth) {
       auto* nested_msg =
           msg->BeginNestedMessage<FakeChildMessage>(1 + depth * 10);
-      BuildNestedMessages(nested_msg, depth + 1);
+      BuildNestedMessages(nested_msg, max_depth, depth + 1);
     }
 
     for (uint32_t i = 129; i <= 256; ++i)
@@ -279,7 +285,7 @@
   std::vector<Message*> nested_msgs;
 
   Message* root_msg = NewMessage();
-  BuildNestedMessages(root_msg);
+  BuildNestedMessages(root_msg, /*max_depth=*/10);
   root_msg->Finalize();
 
   // The main point of this test is to stress the code paths and test for
@@ -291,13 +297,24 @@
   EXPECT_EQ(0xf9e32b65, buf_hash);
 }
 
+TEST_F(MessageTest, DeeplyNested) {
+  std::vector<Message*> nested_msgs;
+
+  Message* root_msg = NewMessage();
+  BuildNestedMessages(root_msg, /*max_depth=*/1000);
+  root_msg->Finalize();
+
+  std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes());
+  size_t buf_hash = SimpleHash(full_buf);
+  EXPECT_EQ(0xc0fde419, buf_hash);
+}
+
 TEST_F(MessageTest, DestructInvalidMessageHandle) {
   FakeRootMessage* msg = NewMessage();
-  EXPECT_DCHECK_DEATH(
-      {
-        MessageHandle<FakeRootMessage> handle(msg);
-        ResetMessage(msg);
-      });
+  EXPECT_DCHECK_DEATH({
+    MessageHandle<FakeRootMessage> handle(msg);
+    ResetMessage(msg);
+  });
 }
 
 TEST_F(MessageTest, MessageHandle) {
diff --git a/src/protozero/proto_decoder_unittest.cc b/src/protozero/proto_decoder_unittest.cc
index 580e959..5f40a89 100644
--- a/src/protozero/proto_decoder_unittest.cc
+++ b/src/protozero/proto_decoder_unittest.cc
@@ -61,29 +61,25 @@
   const uint64_t data_size = 4096 + kPayloadSize;
   std::unique_ptr<uint8_t, perfetto::base::FreeDeleter> data(
       static_cast<uint8_t*>(malloc(data_size)));
-
-  StaticBufferDelegate delegate(data.get(), data_size);
-  ScatteredStreamWriter writer(&delegate);
-  Message message;
-  message.Reset(&writer);
+  StaticBuffered<Message> message(data.get(), data_size);
 
   // Append a valid field.
-  message.AppendVarInt(/*field_id=*/1, 11);
+  message->AppendVarInt(/*field_id=*/1, 11);
 
   // Append a very large field that will be skipped.
   uint8_t raw[10];
   uint8_t* wptr = raw;
   wptr = WriteVarInt(MakeTagLengthDelimited(2), wptr);
   wptr = WriteVarInt(kPayloadSize, wptr);
-  message.AppendRawProtoBytes(raw, static_cast<size_t>(wptr - raw));
+  message->AppendRawProtoBytes(raw, static_cast<size_t>(wptr - raw));
   const uint8_t padding[1024 * 128]{};
   for (size_t i = 0; i < kPayloadSize / sizeof(padding); i++)
-    message.AppendRawProtoBytes(padding, sizeof(padding));
+    message->AppendRawProtoBytes(padding, sizeof(padding));
 
   // Append another valid field.
-  message.AppendVarInt(/*field_id=*/3, 13);
+  message->AppendVarInt(/*field_id=*/3, 13);
 
-  ProtoDecoder decoder(data.get(), static_cast<size_t>(writer.written()));
+  ProtoDecoder decoder(data.get(), message.Finalize());
   Field field = decoder.ReadField();
   ASSERT_EQ(1u, field.id());
   ASSERT_EQ(11, field.as_int32());
@@ -97,16 +93,10 @@
 }
 
 TEST(ProtoDecoderTest, SingleRepeatedField) {
-  Message message;
-  ScatteredHeapBuffer delegate(512, 512);
-  ScatteredStreamWriter writer(&delegate);
-  delegate.set_writer(&writer);
-  message.Reset(&writer);
-  message.AppendVarInt(/*field_id=*/2, 10);
-  delegate.AdjustUsedSizeOfCurrentSlice();
-  auto used_range = delegate.slices()[0].GetUsedRange();
-
-  TypedProtoDecoder<2, true> tpd(used_range.begin, used_range.size());
+  HeapBuffered<Message> message;
+  message->AppendVarInt(/*field_id=*/2, 10);
+  auto data = message.SerializeAsArray();
+  TypedProtoDecoder<2, true> tpd(data.data(), data.size());
   auto it = tpd.GetRepeated<int32_t>(/*field_id=*/2);
   EXPECT_TRUE(it);
   EXPECT_EQ(it.field().as_int32(), 10);
@@ -137,16 +127,11 @@
 }
 
 TEST(ProtoDecoderTest, SingleRepeatedFieldWithExpansion) {
-  Message message;
-  ScatteredHeapBuffer delegate(512, 512);
-  ScatteredStreamWriter writer(&delegate);
-  delegate.set_writer(&writer);
-  message.Reset(&writer);
+  HeapBuffered<Message> message;
   for (int i = 0; i < 2000; i++) {
-    message.AppendVarInt(/*field_id=*/2, i);
+    message->AppendVarInt(/*field_id=*/2, i);
   }
-  std::vector<uint8_t> data = delegate.StitchSlices();
-
+  auto data = message.SerializeAsArray();
   TypedProtoDecoder<2, true> tpd(data.data(), data.size());
   auto it = tpd.GetRepeated<int32_t>(/*field_id=*/2);
   for (int i = 0; i < 2000; i++) {
@@ -166,26 +151,20 @@
 }
 
 TEST(ProtoDecoderTest, RepeatedFields) {
-  Message message;
-  ScatteredHeapBuffer delegate(512, 512);
-  ScatteredStreamWriter writer(&delegate);
-  delegate.set_writer(&writer);
-  message.Reset(&writer);
+  HeapBuffered<Message> message;
 
-  message.AppendVarInt(1, 10);
-  message.AppendVarInt(2, 20);
-  message.AppendVarInt(3, 30);
+  message->AppendVarInt(1, 10);
+  message->AppendVarInt(2, 20);
+  message->AppendVarInt(3, 30);
 
-  message.AppendVarInt(1, 11);
-  message.AppendVarInt(2, 21);
-  message.AppendVarInt(2, 22);
-
-  delegate.AdjustUsedSizeOfCurrentSlice();
-  auto used_range = delegate.slices()[0].GetUsedRange();
+  message->AppendVarInt(1, 11);
+  message->AppendVarInt(2, 21);
+  message->AppendVarInt(2, 22);
 
   // When iterating with the simple decoder we should just see fields in parsing
   // order.
-  ProtoDecoder decoder(used_range.begin, used_range.size());
+  auto data = message.SerializeAsArray();
+  ProtoDecoder decoder(data.data(), data.size());
   std::string fields_seen;
   for (auto fld = decoder.ReadField(); fld.valid(); fld = decoder.ReadField()) {
     fields_seen +=
@@ -193,7 +172,7 @@
   }
   EXPECT_EQ(fields_seen, "1:10;2:20;3:30;1:11;2:21;2:22;");
 
-  TypedProtoDecoder<4, true> tpd(used_range.begin, used_range.size());
+  TypedProtoDecoder<4, true> tpd(data.data(), data.size());
 
   // When parsing with the one-shot decoder and querying the single field id, we
   // should see the last value for each of them, not the first one. This is the
@@ -557,18 +536,14 @@
 // This is a regression test for b/145339282 (DataSourceConfig.for_testing
 // having a very large ID == 268435455 until Android R).
 TEST(ProtoDecoderTest, SkipBigFieldIds) {
-  Message message;
-  ScatteredHeapBuffer delegate(512, 512);
-  ScatteredStreamWriter writer(&delegate);
-  delegate.set_writer(&writer);
-  message.Reset(&writer);
-  message.AppendVarInt(/*field_id=*/1, 11);
-  message.AppendVarInt(/*field_id=*/1000000, 0);  // Will be skipped
-  message.AppendVarInt(/*field_id=*/65535, 99);
-  message.AppendVarInt(/*field_id=*/268435455, 0);  // Will be skipped
-  message.AppendVarInt(/*field_id=*/2, 12);
-  message.AppendVarInt(/*field_id=*/2000000, 0);  // Will be skipped
-  std::vector<uint8_t> data = delegate.StitchSlices();
+  HeapBuffered<Message> message;
+  message->AppendVarInt(/*field_id=*/1, 11);
+  message->AppendVarInt(/*field_id=*/1000000, 0);  // Will be skipped
+  message->AppendVarInt(/*field_id=*/65535, 99);
+  message->AppendVarInt(/*field_id=*/268435455, 0);  // Will be skipped
+  message->AppendVarInt(/*field_id=*/2, 12);
+  message->AppendVarInt(/*field_id=*/2000000, 0);  // Will be skipped
+  auto data = message.SerializeAsArray();
 
   // Check the iterator-based ProtoDecoder.
   {
@@ -606,13 +581,9 @@
 // very big id. Test that we skip it and return an invalid field, instead of
 // geetting stuck in some loop.
 TEST(ProtoDecoderTest, OneBigFieldIdOnly) {
-  Message message;
-  ScatteredHeapBuffer delegate(512, 512);
-  ScatteredStreamWriter writer(&delegate);
-  delegate.set_writer(&writer);
-  message.Reset(&writer);
-  message.AppendVarInt(/*field_id=*/268435455, 0);
-  std::vector<uint8_t> data = delegate.StitchSlices();
+  HeapBuffered<Message> message;
+  message->AppendVarInt(/*field_id=*/268435455, 0);
+  auto data = message.SerializeAsArray();
 
   // Check the iterator-based ProtoDecoder.
   ProtoDecoder decoder(data.data(), data.size());
diff --git a/src/protozero/proto_utils_unittest.cc b/src/protozero/proto_utils_unittest.cc
index 9124490..1cd8082 100644
--- a/src/protozero/proto_utils_unittest.cc
+++ b/src/protozero/proto_utils_unittest.cc
@@ -217,7 +217,7 @@
   uint64_t value = static_cast<uint64_t>(-1);
   const uint8_t* res = ParseVarInt(&good[0], &good[sizeof(good)], &value);
   EXPECT_EQ(&good[sizeof(good)], res);
-  EXPECT_EQ(value, static_cast<uint64_t>(-1ULL));
+  EXPECT_EQ(value, static_cast<uint64_t>(-1));
 
   uint8_t bad[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                    0xff, 0xff, 0xff, 0xff, 0x01};
diff --git a/src/protozero/protoc_plugin/cppgen_plugin.cc b/src/protozero/protoc_plugin/cppgen_plugin.cc
index 514393b..164fb63 100644
--- a/src/protozero/protoc_plugin/cppgen_plugin.cc
+++ b/src/protozero/protoc_plugin/cppgen_plugin.cc
@@ -153,8 +153,10 @@
   cc_printer.Print("#include \"perfetto/protozero/proto_decoder.h\"\n");
   cc_printer.Print("#include \"perfetto/protozero/scattered_heap_buffer.h\"\n");
   cc_printer.Print(kHeader);
+  cc_printer.Print("#if defined(__GNUC__) || defined(__clang__)\n");
   cc_printer.Print("#pragma GCC diagnostic push\n");
   cc_printer.Print("#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n");
+  cc_printer.Print("#endif\n");
 
   // Generate includes for translated types of dependencies.
 
@@ -335,8 +337,10 @@
     h_printer.Print("}  // namespace $n$\n", "n", ns);
     cc_printer.Print("}  // namespace $n$\n", "n", ns);
   }
-
+  cc_printer.Print("#if defined(__GNUC__) || defined(__clang__)\n");
   cc_printer.Print("#pragma GCC diagnostic pop\n");
+  cc_printer.Print("#endif\n");
+
   h_printer.Print("\n#endif  // $g$\n", "g", include_guard);
 
   return true;
@@ -610,24 +614,35 @@
         }
       }
     } else {  // is_repeated()
-      p->Print(
-          "int $n$_size() const { return static_cast<int>($n$_.size()); }\n",
-          "t", GetCppType(field, false), "n", field->lowercase_name());
       p->Print("const std::vector<$t$>& $n$() const { return $n$_; }\n", "t",
                GetCppType(field, false), "n", field->lowercase_name());
       p->Print("std::vector<$t$>* mutable_$n$() { return &$n$_; }\n", "t",
                GetCppType(field, false), "n", field->lowercase_name());
-      p->Print("void clear_$n$() { $n$_.clear(); }\n", "n",
-               field->lowercase_name());
-      if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+
+      // Generate accessors for repeated message types in the .cc file so that
+      // the header doesn't depend on the full definition of all nested types.
+      if (field->type() == TYPE_MESSAGE) {
+        p->Print("int $n$_size() const;\n", "t", GetCppType(field, false), "n",
+                 field->lowercase_name());
+        p->Print("void clear_$n$();\n", "n", field->lowercase_name());
+        p->Print("$t$* add_$n$();\n", "t", GetCppType(field, false), "n",
+                 field->lowercase_name());
+      } else {  // Primitive type.
+        p->Print(
+            "int $n$_size() const { return static_cast<int>($n$_.size()); }\n",
+            "t", GetCppType(field, false), "n", field->lowercase_name());
+        p->Print("void clear_$n$() { $n$_.clear(); }\n", "n",
+                 field->lowercase_name());
         p->Print("void add_$n$($t$ value) { $n$_.emplace_back(value); }\n", "t",
                  GetCppType(field, false), "n", field->lowercase_name());
+        // TODO(primiano): this should be done only for TYPE_MESSAGE.
+        // Unfortuntely we didn't realize before and now we have a bunch of code
+        // that does: *msg->add_int_value() = 42 instead of
+        // msg->add_int_value(42).
+        p->Print(
+            "$t$* add_$n$() { $n$_.emplace_back(); return &$n$_.back(); }\n",
+            "t", GetCppType(field, false), "n", field->lowercase_name());
       }
-      // TODO(primiano): this should be done only for TYPE_MESSAGE. Unfortuntely
-      // we didn't realize before and now we have a bunch of code that does:
-      // *msg->add_int_value() = 42 instead of msg->add_int_value(42).
-      p->Print("$t$* add_$n$() { $n$_.emplace_back(); return &$n$_.back(); }\n",
-               "t", GetCppType(field, false), "n", field->lowercase_name());
     }
   }
   p->Outdent();
@@ -691,6 +706,25 @@
   p->Outdent();
   p->Print("\n}\n\n");
 
+  // Accessors for repeated message fields.
+  for (int i = 0; i < msg->field_count(); i++) {
+    const FieldDescriptor* field = msg->field(i);
+    if (field->options().lazy() || !field->is_repeated() ||
+        field->type() != TYPE_MESSAGE) {
+      continue;
+    }
+    p->Print(
+        "int $c$::$n$_size() const { return static_cast<int>($n$_.size()); }\n",
+        "c", full_name, "t", GetCppType(field, false), "n",
+        field->lowercase_name());
+    p->Print("void $c$::clear_$n$() { $n$_.clear(); }\n", "c", full_name, "n",
+             field->lowercase_name());
+    p->Print(
+        "$t$* $c$::add_$n$() { $n$_.emplace_back(); return &$n$_.back(); }\n",
+        "c", full_name, "t", GetCppType(field, false), "n",
+        field->lowercase_name());
+  }
+
   std::string proto_type = GetFullName(msg, true);
 
   // Generate the ParseFromArray() method definition.
@@ -724,7 +758,7 @@
     } else {
       std::string statement;
       if (field->type() == TYPE_MESSAGE) {
-        statement = "$rval$.ParseFromString(field.as_std_string());\n";
+        statement = "$rval$.ParseFromArray(field.data(), field.size());\n";
       } else {
         if (field->type() == TYPE_SINT32 || field->type() == TYPE_SINT64) {
           // sint32/64 fields are special and need to be zig-zag-decoded.
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 874ee61..531a772 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <stdlib.h>
+
 #include <limits>
 #include <map>
 #include <memory>
@@ -54,7 +56,7 @@
 
 void Assert(bool condition) {
   if (!condition)
-    __builtin_trap();
+    abort();
 }
 
 struct FileDescriptorComp {
@@ -97,7 +99,7 @@
       GenerateEnumDescriptor(enumeration);
     for (const Descriptor* message : messages_)
       GenerateMessageDescriptor(message);
-    for (auto key_value : extensions_)
+    for (const auto& key_value : extensions_)
       GenerateExtension(key_value.first, key_value.second);
     GenerateEpilogue();
     return error_.empty();
@@ -299,7 +301,7 @@
     if (source_->weak_dependency_count() > 0)
       Abort("Weak imports are not supported.");
 
-    // Sanity check. Collect public imports (of collected imports) in DFS order.
+    // Validations. Collect public imports (of collected imports) in DFS order.
     // Visibilty for current proto:
     // - all imports listed in current proto,
     // - public imports of everything imported (recursive).
@@ -326,8 +328,8 @@
     }
 
     // Collect descriptors of messages and enums used in current proto.
-    // It will be used to generate necessary forward declarations and performed
-    // sanity check guarantees that everything lays in the same namespace.
+    // It will be used to generate necessary forward declarations and
+    // check that everything lays in the same namespace.
     for (const Descriptor* message : messages_) {
       for (int i = 0; i < message->field_count(); ++i) {
         const FieldDescriptor* field = message->field(i);
@@ -837,7 +839,8 @@
     // TODO(ddrone): ensure that this code works when containing_type located in
     // other file or namespace.
     stub_h_->Print("class $name$ : public $extendee$ {\n", "name",
-                   extension_name, "extendee", GetCppClassName(base_message));
+                   extension_name, "extendee",
+                   GetCppClassName(base_message, /*full=*/true));
     stub_h_->Print(" public:\n");
     stub_h_->Indent();
     for (const FieldDescriptor* field : descriptors) {
diff --git a/src/protozero/scattered_heap_buffer.cc b/src/protozero/scattered_heap_buffer.cc
index 389c36e..e2fc9af 100644
--- a/src/protozero/scattered_heap_buffer.cc
+++ b/src/protozero/scattered_heap_buffer.cc
@@ -69,10 +69,21 @@
   return slices_.back().GetTotalRange();
 }
 
-std::vector<uint8_t> ScatteredHeapBuffer::StitchSlices() {
+const std::vector<ScatteredHeapBuffer::Slice>&
+ScatteredHeapBuffer::GetSlices() {
   AdjustUsedSizeOfCurrentSlice();
+  return slices_;
+}
+
+std::vector<uint8_t> ScatteredHeapBuffer::StitchSlices() {
+  size_t stitched_size = 0u;
+  const auto& slices = GetSlices();
+  for (const auto& slice : slices)
+    stitched_size += slice.size() - slice.unused_bytes();
+
   std::vector<uint8_t> buffer;
-  for (const auto& slice : slices_) {
+  buffer.reserve(stitched_size);
+  for (const auto& slice : slices) {
     auto used_range = slice.GetUsedRange();
     buffer.insert(buffer.end(), used_range.begin, used_range.end);
   }
@@ -80,9 +91,8 @@
 }
 
 std::vector<protozero::ContiguousMemoryRange> ScatteredHeapBuffer::GetRanges() {
-  AdjustUsedSizeOfCurrentSlice();
   std::vector<protozero::ContiguousMemoryRange> ranges;
-  for (const auto& slice : slices_)
+  for (const auto& slice : GetSlices())
     ranges.push_back(slice.GetUsedRange());
   return ranges;
 }
diff --git a/src/protozero/test/example_proto/test_messages.descriptor.h b/src/protozero/test/example_proto/test_messages.descriptor.h
index e76d3a5..3173971 100644
--- a/src/protozero/test/example_proto/test_messages.descriptor.h
+++ b/src/protozero/test/example_proto/test_messages.descriptor.h
@@ -25,7 +25,7 @@
 // This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
 
 // SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
+// e5c244903aa00cad06faf3d126918306a7fe811e
 // SHA1(src/protozero/test/example_proto/test_messages.proto)
 // 6db2d291c87441f363d941410a1c326078566aa0
 
diff --git a/src/protozero/test/protozero_benchmark.cc b/src/protozero/test/protozero_benchmark.cc
index 4f9fe56..52f2a6c 100644
--- a/src/protozero/test/protozero_benchmark.cc
+++ b/src/protozero/test/protozero_benchmark.cc
@@ -68,8 +68,10 @@
   template <typename T>
   void Append(T x) {
     // The reinterpret_cast is to give favorable alignment guarantees.
-    memcpy(reinterpret_cast<T*>(ptr_), &x, sizeof(x));
-    ptr_ += sizeof(x);
+    // The memcpy will be elided by the compiler, which will emit just a
+    // 64-bit aligned mov instruction.
+    memcpy(reinterpret_cast<void*>(ptr_), &x, sizeof(x));
+    ptr_ += sizeof(uint64_t);
   }
 
   void set_field_int32(int32_t x) { Append(x); }
@@ -80,7 +82,7 @@
 
   SOLMsg* add_field_nested() { return new (this + 1) SOLMsg(); }
 
-  char storage_[sizeof(g_fake_input_simple)];
+  alignas(uint64_t) char storage_[sizeof(g_fake_input_simple) + 8];
   char* ptr_ = &storage_[0];
 };
 
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index a42e905..51a21ff 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -88,8 +88,14 @@
     "importers/ninja/ninja_log_parser.h",
     "importers/proto/args_table_utils.cc",
     "importers/proto/args_table_utils.h",
+    "importers/proto/async_track_set_tracker.cc",
+    "importers/proto/async_track_set_tracker.h",
     "importers/proto/heap_profile_tracker.cc",
     "importers/proto/heap_profile_tracker.h",
+    "importers/proto/memory_tracker_snapshot_module.cc",
+    "importers/proto/memory_tracker_snapshot_module.h",
+    "importers/proto/memory_tracker_snapshot_parser.cc",
+    "importers/proto/memory_tracker_snapshot_parser.h",
     "importers/proto/metadata_tracker.cc",
     "importers/proto/metadata_tracker.h",
     "importers/proto/packet_sequence_state.cc",
@@ -98,11 +104,15 @@
     "importers/proto/profile_module.h",
     "importers/proto/profile_packet_utils.cc",
     "importers/proto/profile_packet_utils.h",
+    "importers/proto/profiler_util.cc",
+    "importers/proto/profiler_util.h",
     "importers/proto/proto_importer_module.cc",
     "importers/proto/proto_importer_module.h",
     "importers/proto/proto_incremental_state.h",
     "importers/proto/proto_trace_parser.cc",
     "importers/proto/proto_trace_parser.h",
+    "importers/proto/proto_trace_reader.cc",
+    "importers/proto/proto_trace_reader.h",
     "importers/proto/proto_trace_tokenizer.cc",
     "importers/proto/proto_trace_tokenizer.h",
     "importers/proto/stack_profile_tracker.cc",
@@ -113,6 +123,8 @@
     "importers/proto/track_event_parser.h",
     "importers/proto/track_event_tokenizer.cc",
     "importers/proto/track_event_tokenizer.h",
+    "importers/proto/track_event_tracker.cc",
+    "importers/proto/track_event_tracker.h",
     "importers/syscalls/syscall_tracker.h",
     "importers/systrace/systrace_line.h",
     "timestamped_trace_piece.h",
@@ -133,6 +145,7 @@
     "../protozero",
     "containers",
     "importers:common",
+    "importers/memory_tracker:graph_processor",
     "storage",
     "tables",
     "types",
@@ -248,7 +261,9 @@
     "../protozero",
     "importers:common",
     "storage",
+    "tables",
     "types",
+    "util",
   ]
   if (enable_perfetto_trace_processor_json) {
     deps += [ "../../gn:jsoncpp" ]
@@ -276,8 +291,10 @@
 if (enable_perfetto_trace_processor_sqlite) {
   source_set("lib") {
     sources = [
-      "dynamic/ancestor_slice_generator.cc",
-      "dynamic/ancestor_slice_generator.h",
+      "dynamic/ancestor_generator.cc",
+      "dynamic/ancestor_generator.h",
+      "dynamic/connected_flow_generator.cc",
+      "dynamic/connected_flow_generator.h",
       "dynamic/descendant_slice_generator.cc",
       "dynamic/descendant_slice_generator.h",
       "dynamic/describe_slice_generator.cc",
@@ -286,8 +303,12 @@
       "dynamic/experimental_counter_dur_generator.h",
       "dynamic/experimental_flamegraph_generator.cc",
       "dynamic/experimental_flamegraph_generator.h",
+      "dynamic/experimental_sched_upid_generator.cc",
+      "dynamic/experimental_sched_upid_generator.h",
       "dynamic/experimental_slice_layout_generator.cc",
       "dynamic/experimental_slice_layout_generator.h",
+      "dynamic/thread_state_generator.cc",
+      "dynamic/thread_state_generator.h",
       "iterator_impl.cc",
       "iterator_impl.h",
       "read_trace.cc",
@@ -303,6 +324,7 @@
       "../../gn:default_deps",
       "../../protos/perfetto/trace/ftrace:zero",
       "../base",
+      "../protozero",
       "analysis",
       "db:lib",
       "importers:common",
@@ -311,6 +333,8 @@
       "storage",
       "tables",
       "types",
+      "util",
+      "util:protozero_to_text",
     ]
     public_deps = [
       "../../gn:sqlite",  # iterator_impl.h includes sqlite3.h.
@@ -321,20 +345,18 @@
     }
   }
 
-  perfetto_host_executable("trace_processor_shell") {
+  executable("trace_processor_shell") {
     deps = [
       ":lib",
       "../../gn:default_deps",
-      "../../gn:protoc_lib",
+      "../../gn:protobuf_full",
+      "../../src/profiling:deobfuscator",
       "../../src/profiling/symbolizer",
       "../../src/profiling/symbolizer:symbolize_database",
       "../base",
       "metrics:lib",
       "util",
     ]
-    if (enable_perfetto_version_gen) {
-      deps += [ "../../gn/standalone:gen_git_revision" ]
-    }
     if (enable_perfetto_trace_processor_linenoise) {
       deps += [ "../../gn:linenoise" ]
     }
@@ -355,7 +377,11 @@
     "forwarding_trace_parser_unittest.cc",
     "importers/ftrace/sched_event_tracker_unittest.cc",
     "importers/fuchsia/fuchsia_trace_utils_unittest.cc",
+    "importers/memory_tracker/graph_processor_unittest.cc",
+    "importers/memory_tracker/graph_unittest.cc",
+    "importers/memory_tracker/raw_process_memory_node_unittest.cc",
     "importers/proto/args_table_utils_unittest.cc",
+    "importers/proto/async_track_set_tracker_unittest.cc",
     "importers/proto/heap_graph_tracker_unittest.cc",
     "importers/proto/heap_profile_tracker_unittest.cc",
     "importers/proto/proto_trace_parser_unittest.cc",
@@ -385,6 +411,7 @@
     "db:unittests",
     "importers:common",
     "importers:unittests",
+    "importers/memory_tracker:graph_processor",
     "rpc:unittests",
     "storage",
     "tables:unittests",
@@ -397,6 +424,7 @@
     sources += [
       "dynamic/experimental_counter_dur_generator_unittest.cc",
       "dynamic/experimental_slice_layout_generator_unittest.cc",
+      "dynamic/thread_state_generator_unittest.cc",
     ]
     deps += [
       ":lib",
@@ -438,7 +466,9 @@
       ":lib",
       "../../gn:default_deps",
       "../../gn:gtest_and_gmock",
+      "../../protos/perfetto/common:zero",
       "../../protos/perfetto/trace:zero",
+      "../../protos/perfetto/trace_processor:zero",
       "../base",
       "../base:test_support",
       "sqlite",
diff --git a/src/trace_processor/chunked_trace_reader.h b/src/trace_processor/chunked_trace_reader.h
index 0d487dc..8dbdd4f 100644
--- a/src/trace_processor/chunked_trace_reader.h
+++ b/src/trace_processor/chunked_trace_reader.h
@@ -29,7 +29,7 @@
 namespace trace_processor {
 
 // Base interface for first stage of parsing pipeline
-// (JsonTraceParser, ProtoTraceTokenizer).
+// (JsonTraceParser, ProtoTraceReader).
 class ChunkedTraceReader {
  public:
   virtual ~ChunkedTraceReader();
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index bfd8b4f..20773dd 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -432,18 +432,13 @@
 
     // Returns the number of set bits.
     uint32_t GetNumBitsSet() const {
-      // We use __builtin_popcountll here as it's available natively for the two
-      // targets we care most about (x64 and WASM).
-      return static_cast<uint32_t>(__builtin_popcountll(word_));
+      return static_cast<uint32_t>(PERFETTO_POPCOUNT(word_));
     }
 
     // Returns the number of set bits up to and including the bit at |idx|.
     uint32_t GetNumBitsSet(uint32_t idx) const {
       PERFETTO_DCHECK(idx < kBits);
-
-      // We use __builtin_popcountll here as it's available natively for the two
-      // targets we care most about (x64 and WASM).
-      return static_cast<uint32_t>(__builtin_popcountll(WordUntil(idx)));
+      return static_cast<uint32_t>(PERFETTO_POPCOUNT(WordUntil(idx)));
     }
 
     // Retains all bits up to and including the bit at |idx| and clears
diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc
index 88a3717..297a0ef 100644
--- a/src/trace_processor/containers/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -117,7 +117,11 @@
 
 RowMap SelectIvWithBv(const std::vector<uint32_t>& iv,
                       const BitVector& selector) {
+  PERFETTO_DCHECK(selector.size() <= iv.size());
+
   std::vector<uint32_t> copy = iv;
+  copy.resize(selector.size());
+
   uint32_t idx = 0;
   auto it = std::remove_if(
       copy.begin(), copy.end(),
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index fde10d5..ac54cb1 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -195,7 +195,7 @@
   ASSERT_EQ(res.Get(1u), 30u);
 }
 
-TEST(RowMapUnittest, SelectRangeWithSmallBitVector) {
+TEST(RowMapUnittest, SelectRangeWithSingleBitVector) {
   RowMap rm(27, 31);
   RowMap picker(BitVector{false, true});
   auto res = rm.SelectRows(picker);
@@ -204,6 +204,16 @@
   ASSERT_EQ(res.Get(0u), 28u);
 }
 
+TEST(RowMapUnittest, SelectRangeWithSmallBitVector) {
+  RowMap rm(27, 31);
+  RowMap picker(BitVector{false, true, true});
+  auto res = rm.SelectRows(picker);
+
+  ASSERT_EQ(res.size(), 2u);
+  ASSERT_EQ(res.Get(0u), 28u);
+  ASSERT_EQ(res.Get(1u), 29u);
+}
+
 TEST(RowMapUnittest, SelectBitVectorWithBitVector) {
   RowMap rm(BitVector{true, false, true, true, false, true});
   RowMap picker(BitVector{true, false, false, true});
@@ -214,7 +224,7 @@
   ASSERT_EQ(res.Get(1u), 5u);
 }
 
-TEST(RowMapUnittest, SelectBitVectorWithSmallBitVector) {
+TEST(RowMapUnittest, SelectBitVectorWithSingleBitVector) {
   RowMap rm(BitVector{true, false, true, true, false, true});
   RowMap picker(BitVector{false, true});
   auto res = rm.SelectRows(picker);
@@ -223,6 +233,16 @@
   ASSERT_EQ(res.Get(0u), 2u);
 }
 
+TEST(RowMapUnittest, SelectBitVectorWithSmallBitVector) {
+  RowMap rm(BitVector{true, false, true, true, false, true});
+  RowMap picker(BitVector{false, true, true});
+  auto res = rm.SelectRows(picker);
+
+  ASSERT_EQ(res.size(), 2u);
+  ASSERT_EQ(res.Get(0u), 2u);
+  ASSERT_EQ(res.Get(1u), 3u);
+}
+
 TEST(RowMapUnittest, SelectIndexVectorWithBitVector) {
   RowMap rm(std::vector<uint32_t>{0u, 2u, 3u, 5u});
   RowMap picker(BitVector{true, false, false, true});
@@ -233,6 +253,16 @@
   ASSERT_EQ(res.Get(1u), 5u);
 }
 
+TEST(RowMapUnittest, SelectIndexVectorWithSmallBitVector) {
+  RowMap rm(std::vector<uint32_t>{0u, 2u, 3u, 5u});
+  RowMap picker(BitVector{false, true, true});
+  auto res = rm.SelectRows(picker);
+
+  ASSERT_EQ(res.size(), 2u);
+  ASSERT_EQ(res.Get(0u), 2u);
+  ASSERT_EQ(res.Get(1u), 3u);
+}
+
 TEST(RowMapUnittest, SelectRangeWithIndexVector) {
   RowMap rm(27, 31);
   RowMap picker(std::vector<uint32_t>{3u, 2u, 0u, 1u, 1u, 3u});
diff --git a/src/trace_processor/containers/string_pool.h b/src/trace_processor/containers/string_pool.h
index 11ae91c..558ff00 100644
--- a/src/trace_processor/containers/string_pool.h
+++ b/src/trace_processor/containers/string_pool.h
@@ -20,6 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <limits>
 #include <unordered_map>
 #include <vector>
 
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index a256cc1..9df9f70 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -38,6 +38,7 @@
   explicit constexpr BaseId(uint32_t v) : value(v) {}
 
   bool operator==(const BaseId& o) const { return o.value == value; }
+  bool operator!=(const BaseId& o) const { return !(*this == o); }
   bool operator<(const BaseId& o) const { return value < o.value; }
 
   uint32_t value;
@@ -111,6 +112,53 @@
     kDense = 1 << 3,
   };
 
+  // Iterator over a column which conforms to std iterator interface
+  // to allow using std algorithms (e.g. upper_bound, lower_bound etc.).
+  class Iterator {
+   public:
+    using iterator_category = std::random_access_iterator_tag;
+    using value_type = SqlValue;
+    using difference_type = uint32_t;
+    using pointer = uint32_t*;
+    using reference = uint32_t&;
+
+    Iterator(const Column* col, uint32_t row) : col_(col), row_(row) {}
+
+    Iterator(const Iterator&) = default;
+    Iterator& operator=(const Iterator&) = default;
+
+    bool operator==(const Iterator& other) const { return other.row_ == row_; }
+    bool operator!=(const Iterator& other) const { return !(*this == other); }
+    bool operator<(const Iterator& other) const { return row_ < other.row_; }
+    bool operator>(const Iterator& other) const { return other < *this; }
+    bool operator<=(const Iterator& other) const { return !(other < *this); }
+    bool operator>=(const Iterator& other) const { return !(*this < other); }
+
+    SqlValue operator*() const { return col_->Get(row_); }
+    Iterator& operator++() {
+      row_++;
+      return *this;
+    }
+    Iterator& operator--() {
+      row_--;
+      return *this;
+    }
+
+    Iterator& operator+=(uint32_t diff) {
+      row_ += diff;
+      return *this;
+    }
+    uint32_t operator-(const Iterator& other) const {
+      return row_ - other.row_;
+    }
+
+    uint32_t row() const { return row_; }
+
+   private:
+    const Column* col_ = nullptr;
+    uint32_t row_ = 0;
+  };
+
   // Flags specified for an id column.
   static constexpr uint32_t kIdFlags = Flag::kSorted | Flag::kNonNull;
 
@@ -344,6 +392,12 @@
   // Returns the JoinKey for this Column.
   JoinKey join_key() const { return JoinKey{col_idx_in_table_}; }
 
+  // Returns an iterator to the first entry in this column.
+  Iterator begin() const { return Iterator(this, 0); }
+
+  // Returns an iterator pointing beyond the last entry in this column.
+  Iterator end() const { return Iterator(this, row_map().size()); }
+
  protected:
   // Returns the backing sparse vector cast to contain data of type T.
   // Should only be called when |type_| == ToColumnType<T>().
@@ -382,49 +436,6 @@
     kId,
   };
 
-  // Iterator over a column which conforms to std iterator interface
-  // to allow using std algorithms (e.g. upper_bound, lower_bound etc.).
-  class Iterator {
-   public:
-    using iterator_category = std::random_access_iterator_tag;
-    using value_type = SqlValue;
-    using difference_type = uint32_t;
-    using pointer = uint32_t*;
-    using reference = uint32_t&;
-
-    Iterator(const Column* col, uint32_t row) : col_(col), row_(row) {}
-
-    Iterator(const Iterator&) = default;
-    Iterator& operator=(const Iterator&) = default;
-
-    bool operator==(const Iterator& other) const { return other.row_ == row_; }
-    bool operator!=(const Iterator& other) const { return !(*this == other); }
-    bool operator<(const Iterator& other) const { return other.row_ < row_; }
-    bool operator>(const Iterator& other) const { return other < *this; }
-    bool operator<=(const Iterator& other) const { return !(other < *this); }
-    bool operator>=(const Iterator& other) const { return !(*this < other); }
-
-    SqlValue operator*() const { return col_->Get(row_); }
-    Iterator& operator++() {
-      row_++;
-      return *this;
-    }
-    Iterator& operator--() {
-      row_--;
-      return *this;
-    }
-
-    Iterator& operator+=(uint32_t diff) {
-      row_ += diff;
-      return *this;
-    }
-    uint32_t operator-(const Iterator& other) { return row_ - other.row_; }
-
-   private:
-    const Column* col_ = nullptr;
-    uint32_t row_ = 0;
-  };
-
   friend class Table;
 
   // Base constructor for this class which all other constructors call into.
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index eab80fc..fb8b73b 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -155,7 +155,7 @@
   Table ExtendWithColumn(const char* name,
                          std::unique_ptr<NullableVector<T>> sv,
                          uint32_t flags) const {
-    PERFETTO_DCHECK(sv->size() == row_count_);
+    PERFETTO_CHECK(sv->size() == row_count_);
     uint32_t size = sv->size();
     uint32_t row_map_count = static_cast<uint32_t>(row_maps_.size());
     Table ret = Copy();
@@ -170,7 +170,7 @@
   Table ExtendWithColumn(const char* name,
                          NullableVector<T>* sv,
                          uint32_t flags) const {
-    PERFETTO_DCHECK(sv->size() == row_count_);
+    PERFETTO_CHECK(sv->size() == row_count_);
     uint32_t size = sv->size();
     uint32_t row_map_count = static_cast<uint32_t>(row_maps_.size());
     Table ret = Copy();
@@ -194,13 +194,13 @@
   }
 
   template <typename T>
-  const TypedColumn<T>* GetTypedColumnByName(const char* name) const {
-    return TypedColumn<T>::FromColumn(GetColumnByName(name));
+  const TypedColumn<T>& GetTypedColumnByName(const char* name) const {
+    return *TypedColumn<T>::FromColumn(GetColumnByName(name));
   }
 
   template <typename T>
-  const IdColumn<T>* GetIdColumnByName(const char* name) const {
-    return IdColumn<T>::FromColumn(GetColumnByName(name));
+  const IdColumn<T>& GetIdColumnByName(const char* name) const {
+    return *IdColumn<T>::FromColumn(GetColumnByName(name));
   }
 
   // Returns the number of columns in the Table.
@@ -211,6 +211,9 @@
   // Returns an iterator into the Table.
   Iterator IterateRows() const { return Iterator(this); }
 
+  // Creates a copy of this table.
+  Table Copy() const;
+
   uint32_t row_count() const { return row_count_; }
   const std::vector<RowMap>& row_maps() const { return row_maps_; }
 
@@ -226,7 +229,6 @@
  private:
   friend class Column;
 
-  Table Copy() const;
   Table CopyExceptRowMaps() const;
 };
 
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index 2bef075..a8fc424 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -34,8 +34,8 @@
 //    overhead.
 // 2. Ergonomics: having to convert back and forth from/to SqlValue causes
 //    signifcant clutter in parts of the code which can already be quite hard
-//    to follow (e.g. trackers like StackProfileTracker which perform cross
-//    checking of various ids).
+//    to follow (e.g. trackers like SequenceStackProfileTracker which perform
+//    cross checking of various ids).
 //
 // Implementation:
 // TypedColumn is implemented as a memberless subclass of Column. This allows
diff --git a/src/trace_processor/dynamic/ancestor_generator.cc b/src/trace_processor/dynamic/ancestor_generator.cc
new file mode 100644
index 0000000..3cb6356
--- /dev/null
+++ b/src/trace_processor/dynamic/ancestor_generator.cc
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/dynamic/ancestor_generator.h"
+
+#include <memory>
+#include <set>
+
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+uint32_t GetConstraintColumnIndex(AncestorGenerator::Ancestor type,
+                                  TraceProcessorContext* context) {
+  switch (type) {
+    case AncestorGenerator::Ancestor::kSlice:
+      return context->storage->slice_table().GetColumnCount();
+    case AncestorGenerator::Ancestor::kStackProfileCallsite:
+      return context->storage->stack_profile_callsite_table().GetColumnCount();
+  }
+  return 0;
+}
+
+template <typename T>
+std::unique_ptr<Table> BuildAncestors(const T& table, uint32_t starting_id) {
+  auto start_row = table.id().IndexOf(typename T::Id(starting_id));
+
+  if (!start_row) {
+    // TODO(lalitm): Ideally this should result in an error, or be filtered out
+    // during ValidateConstraints so we can just dereference |start_row|
+    // directly. However ValidateConstraints doesn't know the value we're
+    // filtering for so can't ensure it exists. For now we return a nullptr
+    // which will cause the query to surface an error with the message "SQL
+    // error: constraint failed".
+    return nullptr;
+  }
+
+  // Build up all the parents row ids, and a new column that includes the
+  // constraint.
+  std::vector<uint32_t> ids;
+  std::unique_ptr<NullableVector<uint32_t>> child_ids(
+      new NullableVector<uint32_t>());
+
+  auto maybe_parent_id = table.parent_id()[*start_row];
+  while (maybe_parent_id) {
+    ids.push_back(maybe_parent_id.value().value);
+    child_ids->Append(starting_id);
+    // Update the loop variable by looking up the next parent_id.
+    maybe_parent_id = table.parent_id()[*table.id().IndexOf(*maybe_parent_id)];
+  }
+  return std::unique_ptr<Table>(
+      new Table(table.Apply(RowMap(std::move(ids)))
+                    .ExtendWithColumn("start_id", std::move(child_ids),
+                                      TypedColumn<uint32_t>::default_flags() |
+                                          TypedColumn<uint32_t>::kHidden)));
+}
+}  // namespace
+
+AncestorGenerator::AncestorGenerator(Ancestor type,
+                                     TraceProcessorContext* context)
+    : type_(type), context_(context) {}
+
+util::Status AncestorGenerator::ValidateConstraints(
+    const QueryConstraints& qc) {
+  const auto& cs = qc.constraints();
+
+  int column = static_cast<int>(GetConstraintColumnIndex(type_, context_));
+  auto id_fn = [column](const QueryConstraints::Constraint& c) {
+    return c.column == column && c.op == SQLITE_INDEX_CONSTRAINT_EQ;
+  };
+  bool has_id_cs = std::find_if(cs.begin(), cs.end(), id_fn) != cs.end();
+  return has_id_cs ? util::OkStatus()
+                   : util::ErrStatus("Failed to find required constraints");
+}
+
+std::unique_ptr<Table> AncestorGenerator::ComputeTable(
+    const std::vector<Constraint>& cs,
+    const std::vector<Order>&) {
+  uint32_t column = GetConstraintColumnIndex(type_, context_);
+  auto it = std::find_if(cs.begin(), cs.end(), [column](const Constraint& c) {
+    return c.col_idx == column && c.op == FilterOp::kEq;
+  });
+  PERFETTO_DCHECK(it != cs.end());
+
+  auto start_id = static_cast<uint32_t>(it->value.AsLong());
+  switch (type_) {
+    case Ancestor::kSlice:
+      return BuildAncestors(context_->storage->slice_table(), start_id);
+    case Ancestor::kStackProfileCallsite:
+      return BuildAncestors(context_->storage->stack_profile_callsite_table(),
+                            start_id);
+  }
+  return nullptr;
+}
+
+Table::Schema AncestorGenerator::CreateSchema() {
+  Table::Schema final_schema;
+  switch (type_) {
+    case Ancestor::kSlice:
+      final_schema = tables::SliceTable::Schema();
+      break;
+    case Ancestor::kStackProfileCallsite:
+      final_schema = tables::StackProfileCallsiteTable::Schema();
+      break;
+  }
+  final_schema.columns.push_back(Table::Schema::Column{
+      "start_id", SqlValue::Type::kLong, /* is_id = */ false,
+      /* is_sorted = */ false, /* is_hidden = */ true});
+  return final_schema;
+}
+
+std::string AncestorGenerator::TableName() {
+  switch (type_) {
+    case Ancestor::kSlice:
+      return "ancestor_slice";
+    case Ancestor::kStackProfileCallsite:
+      return "experimental_ancestor_stack_profile_callsite";
+  }
+  return "ancestor_unknown";
+}
+
+uint32_t AncestorGenerator::EstimateRowCount() {
+  return 1;
+}
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/dynamic/ancestor_slice_generator.h b/src/trace_processor/dynamic/ancestor_generator.h
similarity index 77%
rename from src/trace_processor/dynamic/ancestor_slice_generator.h
rename to src/trace_processor/dynamic/ancestor_generator.h
index 265172e..e39d25d 100644
--- a/src/trace_processor/dynamic/ancestor_slice_generator.h
+++ b/src/trace_processor/dynamic/ancestor_generator.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_SLICE_GENERATOR_H_
-#define SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_SLICE_GENERATOR_H_
+#ifndef SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_GENERATOR_H_
 
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
 
@@ -29,10 +29,11 @@
 // Dynamic table for implementing the  table.
 // See /docs/analysis.md for details about the functionality and usage of this
 // table.
-class AncestorSliceGenerator : public DbSqliteTable::DynamicTableGenerator {
+class AncestorGenerator : public DbSqliteTable::DynamicTableGenerator {
  public:
-  explicit AncestorSliceGenerator(TraceProcessorContext* context);
-  ~AncestorSliceGenerator() override;
+  enum class Ancestor { kSlice = 1, kStackProfileCallsite = 2 };
+
+  AncestorGenerator(Ancestor type, TraceProcessorContext* context);
 
   Table::Schema CreateSchema() override;
   std::string TableName() override;
@@ -42,10 +43,11 @@
                                       const std::vector<Order>& ob) override;
 
  private:
+  Ancestor type_;
   TraceProcessorContext* context_ = nullptr;
 };
 
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_SLICE_GENERATOR_H_
+#endif  // SRC_TRACE_PROCESSOR_DYNAMIC_ANCESTOR_GENERATOR_H_
diff --git a/src/trace_processor/dynamic/ancestor_slice_generator.cc b/src/trace_processor/dynamic/ancestor_slice_generator.cc
deleted file mode 100644
index ff17f91..0000000
--- a/src/trace_processor/dynamic/ancestor_slice_generator.cc
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "src/trace_processor/dynamic/ancestor_slice_generator.h"
-
-#include <memory>
-#include <set>
-
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-AncestorSliceGenerator::AncestorSliceGenerator(TraceProcessorContext* context)
-    : context_(context) {}
-
-AncestorSliceGenerator::~AncestorSliceGenerator() = default;
-
-util::Status AncestorSliceGenerator::ValidateConstraints(
-    const QueryConstraints& qc) {
-  const auto& cs = qc.constraints();
-
-  auto slice_id_fn = [this](const QueryConstraints::Constraint& c) {
-    return c.column == static_cast<int>(
-                           context_->storage->slice_table().GetColumnCount()) &&
-           c.op == SQLITE_INDEX_CONSTRAINT_EQ;
-  };
-  bool has_slice_id_cs =
-      std::find_if(cs.begin(), cs.end(), slice_id_fn) != cs.end();
-
-  return has_slice_id_cs
-             ? util::OkStatus()
-             : util::ErrStatus("Failed to find required constraints");
-}
-
-std::unique_ptr<Table> AncestorSliceGenerator::ComputeTable(
-    const std::vector<Constraint>& cs,
-    const std::vector<Order>&) {
-  using S = tables::SliceTable;
-
-  auto it = std::find_if(cs.begin(), cs.end(), [this](const Constraint& c) {
-    return c.col_idx == context_->storage->slice_table().GetColumnCount() &&
-           c.op == FilterOp::kEq;
-  });
-  PERFETTO_DCHECK(it != cs.end());
-
-  const auto& slice = context_->storage->slice_table();
-  uint32_t child_id = static_cast<uint32_t>(it->value.AsLong());
-  auto start_row = slice.id().IndexOf(S::Id(child_id));
-
-  if (!start_row) {
-    // TODO(lalitm): Ideally this should result in an error, or be filtered out
-    // during ValidateConstraints so we can just dereference |start_row|
-    // directly. However ValidateConstraints doesn't know the value we're
-    // filtering for so can't ensure it exists. For now we return a nullptr
-    // which will cause the query to surface an error with the message "SQL
-    // error: constraint failed".
-    return nullptr;
-  }
-
-  // Build up all the parents row ids, and a new column that includes the
-  // constraint.
-  std::vector<uint32_t> ids;
-  std::unique_ptr<NullableVector<uint32_t>> child_ids(
-      new NullableVector<uint32_t>());
-
-  auto maybe_parent_id = slice.parent_id()[*start_row];
-  while (maybe_parent_id) {
-    ids.push_back(maybe_parent_id.value().value);
-    child_ids->Append(child_id);
-    // Update the loop variable by looking up the next parent_id.
-    maybe_parent_id = slice.parent_id()[*slice.id().IndexOf(*maybe_parent_id)];
-  }
-  return std::unique_ptr<Table>(
-      new Table(slice.Apply(RowMap(std::move(ids)))
-                    .ExtendWithColumn("start_id", std::move(child_ids),
-                                      TypedColumn<uint32_t>::default_flags() |
-                                          TypedColumn<uint32_t>::kHidden)));
-}
-
-Table::Schema AncestorSliceGenerator::CreateSchema() {
-  auto schema = tables::SliceTable::Schema();
-  schema.columns.push_back(Table::Schema::Column{
-      "start_id", SqlValue::Type::kLong, /* is_id = */ false,
-      /* is_sorted = */ false, /* is_hidden = */ true});
-  return schema;
-}
-
-std::string AncestorSliceGenerator::TableName() {
-  return "ancestor_slice";
-}
-
-uint32_t AncestorSliceGenerator::EstimateRowCount() {
-  return 1;
-}
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/dynamic/connected_flow_generator.cc b/src/trace_processor/dynamic/connected_flow_generator.cc
new file mode 100644
index 0000000..d2e0a9e
--- /dev/null
+++ b/src/trace_processor/dynamic/connected_flow_generator.cc
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/dynamic/connected_flow_generator.h"
+
+#include <memory>
+#include <queue>
+#include <set>
+
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ConnectedFlowGenerator::ConnectedFlowGenerator(Direction direction,
+                                               TraceProcessorContext* context)
+    : context_(context), direction_(direction) {}
+
+ConnectedFlowGenerator::~ConnectedFlowGenerator() = default;
+
+util::Status ConnectedFlowGenerator::ValidateConstraints(
+    const QueryConstraints& qc) {
+  const auto& cs = qc.constraints();
+
+  auto flow_id_fn = [this](const QueryConstraints::Constraint& c) {
+    return c.column == static_cast<int>(
+                           context_->storage->flow_table().GetColumnCount()) &&
+           c.op == SQLITE_INDEX_CONSTRAINT_EQ;
+  };
+  bool has_flow_id_cs =
+      std::find_if(cs.begin(), cs.end(), flow_id_fn) != cs.end();
+
+  return has_flow_id_cs
+             ? util::OkStatus()
+             : util::ErrStatus("Failed to find required constraints");
+}
+
+std::vector<uint32_t> ConnectedFlowGenerator::GetConnectedFlowRows(
+    SliceId start_id,
+    Direction dir) {
+  PERFETTO_DCHECK(dir != Direction::BOTH);
+  std::vector<uint32_t> result_rows;
+
+  // TODO: add hash function for SliceId and change this to unordered_set
+  std::set<SliceId> visited_slice_ids;
+  std::queue<SliceId> slice_id_queue({start_id});
+
+  const auto& flow = context_->storage->flow_table();
+  const TypedColumn<SliceId>& start_col =
+      (dir == Direction::FOLLOWING ? flow.slice_out() : flow.slice_in());
+  const TypedColumn<SliceId>& end_col =
+      (dir == Direction::FOLLOWING ? flow.slice_in() : flow.slice_out());
+
+  while (!slice_id_queue.empty()) {
+    SliceId current_slice_id = slice_id_queue.front();
+    slice_id_queue.pop();
+    auto rows = flow.FilterToRowMap({start_col.eq(current_slice_id.value)});
+    for (auto row_it = rows.IterateRows(); row_it; row_it.Next()) {
+      SliceId next_slice_id = end_col[row_it.row()];
+      if (visited_slice_ids.find(next_slice_id) != visited_slice_ids.end()) {
+        continue;
+      }
+
+      visited_slice_ids.insert(next_slice_id);
+      slice_id_queue.push(next_slice_id);
+      result_rows.push_back(row_it.row());
+    }
+  }
+
+  return result_rows;
+}
+
+std::unique_ptr<Table> ConnectedFlowGenerator::ComputeTable(
+    const std::vector<Constraint>& cs,
+    const std::vector<Order>&) {
+  const auto& flow = context_->storage->flow_table();
+  const auto& slice = context_->storage->slice_table();
+
+  auto it = std::find_if(cs.begin(), cs.end(), [&flow](const Constraint& c) {
+    return c.col_idx == flow.GetColumnCount() && c.op == FilterOp::kEq;
+  });
+
+  PERFETTO_DCHECK(it != cs.end());
+
+  SliceId start_id{static_cast<uint32_t>(it->value.AsLong())};
+
+  if (!slice.id().IndexOf(start_id)) {
+    PERFETTO_ELOG("Given slice id is invalid (ConnectedFlowGenerator)");
+    return nullptr;
+  }
+
+  std::vector<uint32_t> result_rows;
+
+  if (direction_ != Direction::PRECEDING) {
+    // FOLLOWING or ALL_CONNECTED
+    auto rows = GetConnectedFlowRows(start_id, Direction::FOLLOWING);
+    result_rows.insert(result_rows.begin(), rows.begin(), rows.end());
+  }
+  if (direction_ != Direction::FOLLOWING) {
+    // PRECEDING or ALL_CONNECTED
+    auto rows = GetConnectedFlowRows(start_id, Direction::PRECEDING);
+    result_rows.insert(result_rows.begin(), rows.begin(), rows.end());
+  }
+
+  // Aditional column for start_id
+  std::unique_ptr<NullableVector<uint32_t>> start_ids(
+      new NullableVector<uint32_t>());
+
+  for (size_t i = 0; i < result_rows.size(); i++) {
+    start_ids->Append(start_id.value);
+  }
+
+  return std::unique_ptr<Table>(
+      new Table(flow.Apply(RowMap(std::move(result_rows)))
+                    .ExtendWithColumn("start_id", std::move(start_ids),
+                                      TypedColumn<uint32_t>::default_flags() |
+                                          TypedColumn<uint32_t>::kHidden)));
+}
+
+Table::Schema ConnectedFlowGenerator::CreateSchema() {
+  auto schema = tables::FlowTable::Schema();
+  schema.columns.push_back(Table::Schema::Column{
+      "start_id", SqlValue::Type::kLong, /* is_id = */ false,
+      /* is_sorted = */ false, /* is_hidden = */ true});
+  return schema;
+}
+
+std::string ConnectedFlowGenerator::TableName() {
+  switch (direction_) {
+    case Direction::BOTH:
+      return "connected_flow";
+    case Direction::FOLLOWING:
+      return "following_flow";
+    case Direction::PRECEDING:
+      return "preceding_flow";
+  }
+  PERFETTO_FATAL("Unexpected ConnectedFlowType");
+}
+
+uint32_t ConnectedFlowGenerator::EstimateRowCount() {
+  return 1;
+}
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/dynamic/connected_flow_generator.h b/src/trace_processor/dynamic/connected_flow_generator.h
new file mode 100644
index 0000000..27715e1
--- /dev/null
+++ b/src/trace_processor/dynamic/connected_flow_generator.h
@@ -0,0 +1,64 @@
+/*
+ * 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_DYNAMIC_CONNECTED_FLOW_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_DYNAMIC_CONNECTED_FLOW_GENERATOR_H_
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+// Implementation of tables: CONNECTED_FLOW, FOLLOWING_FLOW, PERCEEDING_FLOW
+// Searches for all entries of flow events table that are dirrectly or
+// indirectly connected to the given slice (slice id). It is possible to
+// restrict the direction of search.
+class ConnectedFlowGenerator : public DbSqliteTable::DynamicTableGenerator {
+ public:
+  enum class Direction { BOTH = 0, FOLLOWING = 1, PRECEDING = 2 };
+
+  explicit ConnectedFlowGenerator(Direction type,
+                                  TraceProcessorContext* context);
+  ~ConnectedFlowGenerator() override;
+
+  Table::Schema CreateSchema() override;
+  std::string TableName() override;
+  uint32_t EstimateRowCount() override;
+  util::Status ValidateConstraints(const QueryConstraints&) override;
+  std::unique_ptr<Table> ComputeTable(const std::vector<Constraint>& cs,
+                                      const std::vector<Order>& ob) override;
+
+ private:
+  // This function runs BFS on the flow events table as on directed graph
+  // It starts from start_id slice and returns all flow rows that are
+  // directly or indirectly connected to the starting slice.
+  // If dir is FOLLOWING BFS will move in direction (slice_out -> slice_in)
+  // If dir is PRECEDING BFS will move in direction (slice_in -> slice_out)
+  // IMPORTANT: dir must not be set to BOTH for this method
+  std::vector<uint32_t> GetConnectedFlowRows(SliceId start_id, Direction dir);
+
+  TraceProcessorContext* context_ = nullptr;
+  Direction direction_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DYNAMIC_CONNECTED_FLOW_GENERATOR_H_
diff --git a/src/trace_processor/dynamic/experimental_sched_upid_generator.cc b/src/trace_processor/dynamic/experimental_sched_upid_generator.cc
new file mode 100644
index 0000000..1906918
--- /dev/null
+++ b/src/trace_processor/dynamic/experimental_sched_upid_generator.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/dynamic/experimental_sched_upid_generator.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ExperimentalSchedUpidGenerator::ExperimentalSchedUpidGenerator(
+    const tables::SchedSliceTable& sched,
+    const tables::ThreadTable& thread)
+    : sched_slice_table_(&sched), thread_table_(&thread) {}
+ExperimentalSchedUpidGenerator::~ExperimentalSchedUpidGenerator() = default;
+
+Table::Schema ExperimentalSchedUpidGenerator::CreateSchema() {
+  Table::Schema schema = tables::SchedSliceTable::Schema();
+  schema.columns.emplace_back(
+      Table::Schema::Column{"upid", SqlValue::Type::kLong, false /* is_id */,
+                            false /* is_sorted */, false /* is_hidden */});
+  return schema;
+}
+
+std::string ExperimentalSchedUpidGenerator::TableName() {
+  return "experimental_sched_upid";
+}
+
+uint32_t ExperimentalSchedUpidGenerator::EstimateRowCount() {
+  return sched_slice_table_->row_count();
+}
+
+util::Status ExperimentalSchedUpidGenerator::ValidateConstraints(
+    const QueryConstraints&) {
+  return util::OkStatus();
+}
+
+std::unique_ptr<Table> ExperimentalSchedUpidGenerator::ComputeTable(
+    const std::vector<Constraint>&,
+    const std::vector<Order>&) {
+  if (!upid_column_) {
+    upid_column_.reset(new NullableVector<uint32_t>(ComputeUpidColumn()));
+  }
+  return std::unique_ptr<Table>(new Table(sched_slice_table_->ExtendWithColumn(
+      "upid", upid_column_.get(),
+      TypedColumn<base::Optional<uint32_t>>::default_flags())));
+}
+
+NullableVector<uint32_t> ExperimentalSchedUpidGenerator::ComputeUpidColumn() {
+  NullableVector<uint32_t> upid;
+  for (uint32_t i = 0; i < sched_slice_table_->row_count(); ++i) {
+    upid.Append(thread_table_->upid()[sched_slice_table_->utid()[i]]);
+  }
+  return upid;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/dynamic/experimental_sched_upid_generator.h b/src/trace_processor/dynamic/experimental_sched_upid_generator.h
new file mode 100644
index 0000000..09e0dfa
--- /dev/null
+++ b/src/trace_processor/dynamic/experimental_sched_upid_generator.h
@@ -0,0 +1,53 @@
+/*
+ * 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_DYNAMIC_EXPERIMENTAL_SCHED_UPID_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_DYNAMIC_EXPERIMENTAL_SCHED_UPID_GENERATOR_H_
+
+#include <set>
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class ExperimentalSchedUpidGenerator
+    : public DbSqliteTable::DynamicTableGenerator {
+ public:
+  ExperimentalSchedUpidGenerator(const tables::SchedSliceTable&,
+                                 const tables::ThreadTable&);
+  virtual ~ExperimentalSchedUpidGenerator() override;
+
+  Table::Schema CreateSchema() override;
+  std::string TableName() override;
+  uint32_t EstimateRowCount() override;
+  util::Status ValidateConstraints(const QueryConstraints&) override;
+  std::unique_ptr<Table> ComputeTable(const std::vector<Constraint>&,
+                                      const std::vector<Order>&) override;
+
+ private:
+  NullableVector<uint32_t> ComputeUpidColumn();
+
+  const tables::SchedSliceTable* sched_slice_table_;
+  const tables::ThreadTable* thread_table_;
+  std::unique_ptr<NullableVector<uint32_t>> upid_column_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DYNAMIC_EXPERIMENTAL_SCHED_UPID_GENERATOR_H_
diff --git a/src/trace_processor/dynamic/experimental_slice_layout_generator.cc b/src/trace_processor/dynamic/experimental_slice_layout_generator.cc
index 132d7dc..297b7e0 100644
--- a/src/trace_processor/dynamic/experimental_slice_layout_generator.cc
+++ b/src/trace_processor/dynamic/experimental_slice_layout_generator.cc
@@ -95,7 +95,36 @@
 
   StringPool::Id filter_id =
       string_pool_->InternString(base::StringView(filter_string));
-  return AddLayoutColumn(*slice_table_, selected_tracks, filter_id);
+
+  // Try and find the table in the cache.
+  auto it = layout_table_cache_.find(filter_id);
+  if (it != layout_table_cache_.end()) {
+    return std::unique_ptr<Table>(new Table(it->second.Copy()));
+  }
+
+  // Find all the slices for the tracks we want to filter and create a RowMap
+  // out of them.
+  // TODO(lalitm): Update this to use iterator (as this code will be slow after
+  // the event table is implemented).
+  // TODO(lalitm): consider generalising this by adding OR constraint support to
+  // Constraint and Table::Filter. We definitely want to wait until we have more
+  // usecases before implementing that though because it will be a significant
+  // amount of work.
+  RowMap rm;
+  for (uint32_t i = 0; i < slice_table_->row_count(); ++i) {
+    if (selected_tracks.count(slice_table_->track_id()[i]) > 0) {
+      rm.Insert(i);
+    }
+  }
+
+  // Apply the row map to the table to cut down on the number of rows we have to
+  // go through.
+  Table filtered_table = slice_table_->Apply(std::move(rm));
+
+  // Compute the table and add it to the cache for future use.
+  Table layout_table = ComputeLayoutTable(filtered_table, filter_id);
+  auto res = layout_table_cache_.emplace(filter_id, std::move(layout_table));
+  return std::unique_ptr<Table>(new Table(res.first->second.Copy()));
 }
 
 // Build up a table of slice id -> root slice id by observing each
@@ -143,33 +172,26 @@
 // 3. Go though each slice and give it a layout_depth by summing it's
 //    current depth and the root layout_depth of the stalactite it belongs to.
 //
-std::unique_ptr<Table> ExperimentalSliceLayoutGenerator::AddLayoutColumn(
+Table ExperimentalSliceLayoutGenerator::ComputeLayoutTable(
     const Table& table,
-    const std::set<TrackId>& selected,
     StringPool::Id filter_id) {
-  const auto& track_id_col =
-      *table.GetTypedColumnByName<tables::TrackTable::Id>("track_id");
-  const auto& id_col = *table.GetIdColumnByName<tables::SliceTable::Id>("id");
-  const auto& parent_id_col =
-      *table.GetTypedColumnByName<base::Optional<tables::SliceTable::Id>>(
-          "parent_id");
-  const auto& depth_col = *table.GetTypedColumnByName<uint32_t>("depth");
-  const auto& ts_col = *table.GetTypedColumnByName<int64_t>("ts");
-  const auto& dur_col = *table.GetTypedColumnByName<int64_t>("dur");
-
   std::map<tables::SliceTable::Id, GroupInfo> groups;
   // Map of id -> root_id
   std::map<tables::SliceTable::Id, tables::SliceTable::Id> id_map;
 
+  const auto& id_col = table.GetIdColumnByName<tables::SliceTable::Id>("id");
+  const auto& parent_id_col =
+      table.GetTypedColumnByName<base::Optional<tables::SliceTable::Id>>(
+          "parent_id");
+  const auto& depth_col = table.GetTypedColumnByName<uint32_t>("depth");
+  const auto& ts_col = table.GetTypedColumnByName<int64_t>("ts");
+  const auto& dur_col = table.GetTypedColumnByName<int64_t>("dur");
+
   // Step 1:
   // Find the bounding box (start ts, end ts, and max depth) for each group
-  // TODO(lalitm): Update this to use iterator (will be slow after event table)
+  // TODO(lalitm): Update this to use iterator (as this code will be slow after
+  // the event table is implemented)
   for (uint32_t i = 0; i < table.row_count(); ++i) {
-    TrackId track_id = track_id_col[i];
-    if (selected.count(track_id) == 0) {
-      continue;
-    }
-
     tables::SliceTable::Id id = id_col[i];
     base::Optional<tables::SliceTable::Id> parent_id = parent_id_col[i];
     uint32_t depth = depth_col[i];
@@ -259,31 +281,20 @@
       new NullableVector<StringPool::Id>());
 
   for (uint32_t i = 0; i < table.row_count(); ++i) {
-    TrackId track_id = track_id_col[i];
     tables::SliceTable::Id id = id_col[i];
     uint32_t depth = depth_col[i];
-    if (selected.count(track_id) == 0) {
-      // Don't care about depth for slices from non-selected tracks:
-      layout_depth_column->Append(0);
-      // We (ab)use this column to also filter out all the slices we don't care
-      // about by giving it a different value.
-      filter_column->Append(empty_string_id_);
-    } else {
-      // Each slice depth is it's current slice depth + root slice depth of the
-      // group:
-      layout_depth_column->Append(depth + groups.at(id_map[id]).layout_depth);
-      // We must set this to the value we got in the constraint to ensure our
-      // rows are not filtered out:
-      filter_column->Append(filter_id);
-    }
+    // Each slice depth is it's current slice depth + root slice depth of the
+    // group:
+    layout_depth_column->Append(depth + groups.at(id_map[id]).layout_depth);
+    // We must set this to the value we got in the constraint to ensure our
+    // rows are not filtered out:
+    filter_column->Append(filter_id);
   }
-
-  return std::unique_ptr<Table>(new Table(
-      table
-          .ExtendWithColumn("layout_depth", std::move(layout_depth_column),
-                            TypedColumn<int64_t>::default_flags())
-          .ExtendWithColumn("filter_track_ids", std::move(filter_column),
-                            TypedColumn<StringPool::Id>::default_flags())));
+  return table
+      .ExtendWithColumn("layout_depth", std::move(layout_depth_column),
+                        TypedColumn<int64_t>::default_flags())
+      .ExtendWithColumn("filter_track_ids", std::move(filter_column),
+                        TypedColumn<StringPool::Id>::default_flags());
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/dynamic/experimental_slice_layout_generator.h b/src/trace_processor/dynamic/experimental_slice_layout_generator.h
index 3f5e6ff..4b033f7 100644
--- a/src/trace_processor/dynamic/experimental_slice_layout_generator.h
+++ b/src/trace_processor/dynamic/experimental_slice_layout_generator.h
@@ -43,14 +43,16 @@
                                       const std::vector<Order>&) override;
 
  private:
-  std::unique_ptr<Table> AddLayoutColumn(const Table& table,
-                                         const std::set<TrackId>& selected,
-                                         StringPool::Id filter_id);
+  Table ComputeLayoutTable(const Table& table, StringPool::Id filter_id);
   tables::SliceTable::Id InsertSlice(
       std::map<tables::SliceTable::Id, tables::SliceTable::Id>& id_map,
       tables::SliceTable::Id id,
       base::Optional<tables::SliceTable::Id> parent_id);
 
+  // TODO(lalitm): remove this cache and move to having explicitly scoped
+  // lifetimes of dynamic tables.
+  std::unordered_map<StringId, Table> layout_table_cache_;
+
   StringPool* string_pool_;
   const tables::SliceTable* slice_table_;
   const StringPool::Id empty_string_id_;
diff --git a/src/trace_processor/dynamic/thread_state_generator.cc b/src/trace_processor/dynamic/thread_state_generator.cc
new file mode 100644
index 0000000..8eb16e2
--- /dev/null
+++ b/src/trace_processor/dynamic/thread_state_generator.cc
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/dynamic/thread_state_generator.h"
+
+#include <memory>
+#include <set>
+
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ThreadStateGenerator::ThreadStateGenerator(TraceProcessorContext* context)
+    : running_string_id_(context->storage->InternString("Running")),
+      runnable_string_id_(context->storage->InternString("R")),
+      context_(context) {}
+
+ThreadStateGenerator::~ThreadStateGenerator() = default;
+
+util::Status ThreadStateGenerator::ValidateConstraints(
+    const QueryConstraints&) {
+  return util::OkStatus();
+}
+
+std::unique_ptr<Table> ThreadStateGenerator::ComputeTable(
+    const std::vector<Constraint>&,
+    const std::vector<Order>&) {
+  if (!unsorted_thread_state_table_) {
+    int64_t trace_end_ts =
+        context_->storage->GetTraceTimestampBoundsNs().second;
+
+    unsorted_thread_state_table_ = ComputeThreadStateTable(trace_end_ts);
+
+    // We explicitly sort by ts here as ComputeThreadStateTable does not insert
+    // rows in sorted order but we expect our clients to always want to sort
+    // on ts. Writing ComputeThreadStateTable to insert in sorted order is
+    // more trouble than its worth.
+    sorted_thread_state_table_ = unsorted_thread_state_table_->Sort(
+        {unsorted_thread_state_table_->ts().ascending()});
+  }
+  PERFETTO_CHECK(sorted_thread_state_table_);
+  return std::unique_ptr<Table>(new Table(sorted_thread_state_table_->Copy()));
+}
+
+std::unique_ptr<tables::ThreadStateTable>
+ThreadStateGenerator::ComputeThreadStateTable(int64_t trace_end_ts) {
+  std::unique_ptr<tables::ThreadStateTable> table(new tables::ThreadStateTable(
+      context_->storage->mutable_string_pool(), nullptr));
+
+  const auto& raw_sched = context_->storage->sched_slice_table();
+  const auto& instants = context_->storage->instant_table();
+
+  // In both tables, exclude utid == 0 which represents the idle thread.
+  Table sched = raw_sched.Filter({raw_sched.utid().ne(0)});
+  Table waking = instants.Filter(
+      {instants.name().eq("sched_waking"), instants.ref().ne(0)});
+
+  // We prefer to use waking if at all possible and fall back to wakeup if not
+  // available.
+  if (waking.row_count() == 0) {
+    waking = instants.Filter(
+        {instants.name().eq("sched_wakeup"), instants.ref().ne(0)});
+  }
+
+  Table sched_blocked_reason = instants.Filter(
+      {instants.name().eq("sched_blocked_reason"), instants.ref().ne(0)});
+
+  const auto& sched_ts_col = sched.GetTypedColumnByName<int64_t>("ts");
+  const auto& waking_ts_col = waking.GetTypedColumnByName<int64_t>("ts");
+  const auto& blocked_ts_col =
+      sched_blocked_reason.GetTypedColumnByName<int64_t>("ts");
+
+  uint32_t sched_idx = 0;
+  uint32_t waking_idx = 0;
+  uint32_t blocked_idx = 0;
+  std::unordered_map<UniqueTid, ThreadSchedInfo> state_map;
+  while (sched_idx < sched.row_count() || waking_idx < waking.row_count() ||
+         blocked_idx < sched_blocked_reason.row_count()) {
+    int64_t sched_ts = sched_idx < sched.row_count()
+                           ? sched_ts_col[sched_idx]
+                           : std::numeric_limits<int64_t>::max();
+    int64_t waking_ts = waking_idx < waking.row_count()
+                            ? waking_ts_col[waking_idx]
+                            : std::numeric_limits<int64_t>::max();
+    int64_t blocked_ts = blocked_idx < sched_blocked_reason.row_count()
+                             ? blocked_ts_col[blocked_idx]
+                             : std::numeric_limits<int64_t>::max();
+
+    // We go through all tables, picking the earliest timestamp from any
+    // to process that event.
+    int64_t min_ts = std::min({sched_ts, waking_ts, blocked_ts});
+    if (min_ts == sched_ts) {
+      AddSchedEvent(sched, sched_idx++, state_map, trace_end_ts, table.get());
+    } else if (min_ts == waking_ts) {
+      AddWakingEvent(waking, waking_idx++, state_map);
+    } else /* (min_ts == blocked_ts) */ {
+      AddBlockedReasonEvent(sched_blocked_reason, blocked_idx++, state_map);
+    }
+  }
+
+  // At the end, go through and flush any remaining pending events.
+  for (const auto& utid_to_pending_info : state_map) {
+    UniqueTid utid = utid_to_pending_info.first;
+    const ThreadSchedInfo& pending_info = utid_to_pending_info.second;
+    FlushPendingEventsForThread(utid, pending_info, table.get(), base::nullopt);
+  }
+
+  return table;
+}
+
+void ThreadStateGenerator::AddSchedEvent(
+    const Table& sched,
+    uint32_t sched_idx,
+    std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map,
+    int64_t trace_end_ts,
+    tables::ThreadStateTable* table) {
+  int64_t ts = sched.GetTypedColumnByName<int64_t>("ts")[sched_idx];
+  UniqueTid utid = sched.GetTypedColumnByName<uint32_t>("utid")[sched_idx];
+  ThreadSchedInfo* info = &state_map[utid];
+
+  // Flush the info and reset so we don't have any leftover data on the next
+  // round.
+  FlushPendingEventsForThread(utid, *info, table, ts);
+  *info = {};
+
+  // Undo the expansion of the final sched slice for each CPU to the end of the
+  // trace by setting the duration back to -1. This counteracts the code in
+  // SchedEventTracker::FlushPendingEvents
+  // TODO(lalitm): remove this hack when we stop expanding the last slice to the
+  // end of the trace.
+  int64_t dur = sched.GetTypedColumnByName<int64_t>("dur")[sched_idx];
+  if (ts + dur == trace_end_ts) {
+    dur = -1;
+  }
+
+  // Now add the sched slice itself as "Running" with the other fields
+  // unchanged.
+  tables::ThreadStateTable::Row sched_row;
+  sched_row.ts = ts;
+  sched_row.dur = dur;
+  sched_row.cpu = sched.GetTypedColumnByName<uint32_t>("cpu")[sched_idx];
+  sched_row.state = running_string_id_;
+  sched_row.utid = utid;
+  table->Insert(sched_row);
+
+  // If the sched row had a negative duration, don't add any descheduled slice
+  // because it would be meaningless.
+  if (sched_row.dur == -1) {
+    return;
+  }
+
+  // This will be flushed to the table on the next sched slice (or the very end
+  // of the big loop).
+  info->desched_ts = ts + dur;
+  info->desched_end_state =
+      sched.GetTypedColumnByName<StringId>("end_state")[sched_idx];
+}
+
+void ThreadStateGenerator::AddWakingEvent(
+    const Table& waking,
+    uint32_t waking_idx,
+    std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map) {
+  int64_t ts = waking.GetTypedColumnByName<int64_t>("ts")[waking_idx];
+  UniqueTid utid = static_cast<UniqueTid>(
+      waking.GetTypedColumnByName<int64_t>("ref")[waking_idx]);
+  ThreadSchedInfo* info = &state_map[utid];
+
+  // As counter-intuitive as it seems, occassionally we can get a waking
+  // event for a thread which is currently running.
+  //
+  // There are two cases when this can happen:
+  // 1. The kernel legitimately send a waking event for a "running" thread
+  //    because the thread was woken up before the kernel switched away
+  //    from it. In this case, the waking timestamp will be in the past
+  //    because we added the descheduled slice when we processed the sched
+  //    event.
+  // 2. We're close to the end of the trace or had data-loss and we missed
+  //    the switch out event for a thread but we see a waking after.
+
+  // Case 1 described above. In this situation, we should drop the waking
+  // entirely.
+  if (info->desched_ts && *info->desched_ts > ts) {
+    return;
+  }
+
+  // For case 2 and otherwise, we should just note the fact that the thread
+  // became runnable at this time. Note that we cannot check if runnable is
+  // already not set because we could have data-loss which leads to us getting
+  // back to back waking for a single thread.
+  info->runnable_ts = ts;
+}
+
+Table::Schema ThreadStateGenerator::CreateSchema() {
+  auto schema = tables::ThreadStateTable::Schema();
+
+  // Because we expect our users to generally want ordered by ts, we set the
+  // ordering for the schema to match our forced sort pass in ComputeTable.
+  auto ts_it = std::find_if(
+      schema.columns.begin(), schema.columns.end(),
+      [](const Table::Schema::Column& col) { return col.name == "ts"; });
+  ts_it->is_sorted = true;
+
+  return schema;
+}
+
+void ThreadStateGenerator::FlushPendingEventsForThread(
+    UniqueTid utid,
+    const ThreadSchedInfo& info,
+    tables::ThreadStateTable* table,
+    base::Optional<int64_t> end_ts) {
+  // First, let's flush the descheduled period (if any) to the table.
+  if (info.desched_ts) {
+    PERFETTO_DCHECK(info.desched_end_state);
+
+    int64_t dur;
+    if (end_ts) {
+      int64_t desched_end_ts = info.runnable_ts ? *info.runnable_ts : *end_ts;
+      dur = desched_end_ts - *info.desched_ts;
+    } else {
+      dur = -1;
+    }
+
+    tables::ThreadStateTable::Row row;
+    row.ts = *info.desched_ts;
+    row.dur = dur;
+    row.state = *info.desched_end_state;
+    row.utid = utid;
+    row.io_wait = info.io_wait;
+    table->Insert(row);
+  }
+
+  // Next, flush the runnable period (if any) to the table.
+  if (info.runnable_ts) {
+    tables::ThreadStateTable::Row row;
+    row.ts = *info.runnable_ts;
+    row.dur = end_ts ? *end_ts - row.ts : -1;
+    row.state = runnable_string_id_;
+    row.utid = utid;
+    table->Insert(row);
+  }
+}
+
+void ThreadStateGenerator::AddBlockedReasonEvent(
+    const Table& blocked_reason,
+    uint32_t blocked_idx,
+    std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map) {
+  const auto& utid_col = blocked_reason.GetTypedColumnByName<int64_t>("ref");
+  const auto& arg_set_id_col =
+      blocked_reason.GetTypedColumnByName<uint32_t>("arg_set_id");
+
+  UniqueTid utid = static_cast<UniqueTid>(utid_col[blocked_idx]);
+  uint32_t arg_set_id = arg_set_id_col[blocked_idx];
+  ThreadSchedInfo& info = state_map[utid];
+
+  base::Optional<Variadic> opt_value;
+  util::Status status =
+      context_->storage->ExtractArg(arg_set_id, "io_wait", &opt_value);
+
+  // We can't do anything better than ignoring any errors here.
+  // TODO(lalitm): see if there's a better way to handle this.
+  if (!status.ok() || !opt_value) {
+    return;
+  }
+
+  PERFETTO_CHECK(opt_value->type == Variadic::Type::kBool);
+  info.io_wait = opt_value->bool_value;
+}
+
+std::string ThreadStateGenerator::TableName() {
+  return "thread_state";
+}
+
+uint32_t ThreadStateGenerator::EstimateRowCount() {
+  return context_->storage->sched_slice_table().row_count();
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/dynamic/thread_state_generator.h b/src/trace_processor/dynamic/thread_state_generator.h
new file mode 100644
index 0000000..ca51f9b
--- /dev/null
+++ b/src/trace_processor/dynamic/thread_state_generator.h
@@ -0,0 +1,89 @@
+/*
+ * 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_DYNAMIC_THREAD_STATE_GENERATOR_H_
+#define SRC_TRACE_PROCESSOR_DYNAMIC_THREAD_STATE_GENERATOR_H_
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+// Dynamic table implementing the thread state table.
+// This table is a basically the same as sched with extra information added
+// about wakeups (obtained from sched_waking/sched_wakeup).
+class ThreadStateGenerator : public DbSqliteTable::DynamicTableGenerator {
+ public:
+  explicit ThreadStateGenerator(TraceProcessorContext* context);
+  ~ThreadStateGenerator() override;
+
+  Table::Schema CreateSchema() override;
+  std::string TableName() override;
+  uint32_t EstimateRowCount() override;
+  util::Status ValidateConstraints(const QueryConstraints&) override;
+  std::unique_ptr<Table> ComputeTable(const std::vector<Constraint>& cs,
+                                      const std::vector<Order>& ob) override;
+
+  // Visible for testing.
+  std::unique_ptr<tables::ThreadStateTable> ComputeThreadStateTable(
+      int64_t trace_end_ts);
+
+ private:
+  struct ThreadSchedInfo {
+    base::Optional<int64_t> desched_ts;
+    base::Optional<StringId> desched_end_state;
+    base::Optional<bool> io_wait;
+    base::Optional<int64_t> runnable_ts;
+  };
+
+  void AddSchedEvent(const Table& sched,
+                     uint32_t sched_idx,
+                     std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map,
+                     int64_t trace_end_ts,
+                     tables::ThreadStateTable* table);
+
+  void AddWakingEvent(
+      const Table& wakeup,
+      uint32_t wakeup_idx,
+      std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map);
+
+  void AddBlockedReasonEvent(
+      const Table& blocked_reason,
+      uint32_t blocked_idx,
+      std::unordered_map<UniqueTid, ThreadSchedInfo>& state_map);
+
+  void FlushPendingEventsForThread(UniqueTid utid,
+                                   const ThreadSchedInfo&,
+                                   tables::ThreadStateTable* table,
+                                   base::Optional<int64_t> end_ts);
+
+  std::unique_ptr<tables::ThreadStateTable> unsorted_thread_state_table_;
+  base::Optional<Table> sorted_thread_state_table_;
+
+  const StringId running_string_id_;
+  const StringId runnable_string_id_;
+
+  TraceProcessorContext* context_ = nullptr;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DYNAMIC_THREAD_STATE_GENERATOR_H_
diff --git a/src/trace_processor/dynamic/thread_state_generator_unittest.cc b/src/trace_processor/dynamic/thread_state_generator_unittest.cc
new file mode 100644
index 0000000..e9cf5d6
--- /dev/null
+++ b/src/trace_processor/dynamic/thread_state_generator_unittest.cc
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/dynamic/thread_state_generator.h"
+
+#include <algorithm>
+
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+class ThreadStateGeneratorUnittest : public testing::Test {
+ public:
+  struct Ts {
+    int64_t ts;
+  };
+
+  ThreadStateGeneratorUnittest() : idle_thread_(0), thread_a_(1), thread_b_(2) {
+    context_.storage.reset(new TraceStorage());
+    context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+    context_.args_tracker.reset(new ArgsTracker(&context_));
+    thread_state_generator_.reset(new ThreadStateGenerator(&context_));
+  }
+
+  void ForwardSchedTo(Ts ts) { sched_insert_ts_ = ts.ts; }
+
+  void AddWaking(Ts ts, UniqueTid utid) {
+    tables::InstantTable::Row row;
+    row.ts = ts.ts;
+    row.ref = utid;
+    row.name = context_.storage->InternString("sched_waking");
+    context_.storage->mutable_instant_table()->Insert(row);
+  }
+
+  void AddWakup(Ts ts, UniqueTid utid) {
+    tables::InstantTable::Row row;
+    row.ts = ts.ts;
+    row.ref = utid;
+    row.name = context_.storage->InternString("sched_wakeup");
+    context_.storage->mutable_instant_table()->Insert(row);
+  }
+
+  void AddSched(base::Optional<Ts> end, UniqueTid utid, const char* end_state) {
+    StringId end_state_id = context_.storage->InternString(end_state);
+
+    tables::SchedSliceTable::Row row;
+
+    // cpu is hardcoded because it doesn't matter for the algorithm and is
+    // just passed through unchanged.
+    row.cpu = 0;
+
+    row.ts = sched_insert_ts_;
+    row.dur = end ? end->ts - row.ts : -1;
+    row.utid = utid;
+    row.end_state = end_state_id;
+    context_.storage->mutable_sched_slice_table()->Insert(row);
+
+    sched_insert_ts_ = end ? end->ts : -1;
+  }
+
+  void AddBlockedReason(Ts ts, UniqueTid utid, bool io_wait) {
+    tables::InstantTable::Row row;
+    row.ts = ts.ts;
+    row.ref = utid;
+    row.name = context_.storage->InternString("sched_blocked_reason");
+
+    auto id = context_.storage->mutable_instant_table()->Insert(row).id;
+    auto inserter = context_.args_tracker->AddArgsTo(id);
+    inserter.AddArg(context_.storage->InternString("io_wait"),
+                    Variadic::Boolean(io_wait));
+    context_.args_tracker->Flush();
+  }
+
+  void RunThreadStateComputation(Ts trace_end_ts = Ts{
+                                     std::numeric_limits<int64_t>::max()}) {
+    unsorted_table_ =
+        thread_state_generator_->ComputeThreadStateTable(trace_end_ts.ts);
+    table_.reset(
+        new Table(unsorted_table_->Sort({unsorted_table_->ts().ascending()})));
+  }
+
+  void VerifyThreadState(Ts from,
+                         base::Optional<Ts> to,
+                         UniqueTid utid,
+                         const char* state,
+                         base::Optional<bool> io_wait = base::nullopt) {
+    uint32_t row = thread_state_verify_row_++;
+
+    const auto& ts_col = table_->GetTypedColumnByName<int64_t>("ts");
+    const auto& dur_col = table_->GetTypedColumnByName<int64_t>("dur");
+    const auto& utid_col = table_->GetTypedColumnByName<UniqueTid>("utid");
+    const auto& cpu_col =
+        table_->GetTypedColumnByName<base::Optional<uint32_t>>("cpu");
+    const auto& end_state_col = table_->GetTypedColumnByName<StringId>("state");
+    const auto& io_wait_col =
+        table_->GetTypedColumnByName<base::Optional<uint32_t>>("io_wait");
+
+    ASSERT_LT(row, table_->row_count());
+    ASSERT_EQ(ts_col[row], from.ts);
+    ASSERT_EQ(dur_col[row], to ? to->ts - from.ts : -1);
+    ASSERT_EQ(utid_col[row], utid);
+    if (state == kRunning) {
+      ASSERT_EQ(cpu_col[row], 0u);
+    } else {
+      ASSERT_EQ(cpu_col[row], base::nullopt);
+    }
+    ASSERT_EQ(end_state_col.GetString(row), base::StringView(state));
+
+    base::Optional<uint32_t> mapped_io_wait =
+        io_wait ? base::make_optional(static_cast<uint32_t>(*io_wait))
+                : base::nullopt;
+    ASSERT_EQ(io_wait_col[row], mapped_io_wait);
+  }
+
+  void VerifyEndOfThreadState() {
+    ASSERT_EQ(thread_state_verify_row_, table_->row_count());
+  }
+
+ protected:
+  static constexpr char kRunning[] = "Running";
+
+  const UniqueTid idle_thread_;
+  const UniqueTid thread_a_;
+  const UniqueTid thread_b_;
+
+ private:
+  TraceProcessorContext context_;
+
+  int64_t sched_insert_ts_ = 0;
+
+  uint32_t thread_state_verify_row_ = 0;
+
+  std::unique_ptr<ThreadStateGenerator> thread_state_generator_;
+  std::unique_ptr<tables::ThreadStateTable> unsorted_table_;
+  std::unique_ptr<Table> table_;
+};
+
+constexpr char ThreadStateGeneratorUnittest::kRunning[];
+
+TEST_F(ThreadStateGeneratorUnittest, MultipleThreadWithOnlySched) {
+  ForwardSchedTo(Ts{0});
+  AddSched(Ts{10}, thread_a_, "S");
+  AddSched(Ts{15}, thread_b_, "D");
+  AddSched(Ts{20}, thread_a_, "R");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{0}, Ts{10}, thread_a_, kRunning);
+  VerifyThreadState(Ts{10}, Ts{15}, thread_b_, kRunning);
+  VerifyThreadState(Ts{10}, Ts{15}, thread_a_, "S");
+  VerifyThreadState(Ts{15}, Ts{20}, thread_a_, kRunning);
+  VerifyThreadState(Ts{15}, base::nullopt, thread_b_, "D");
+  VerifyThreadState(Ts{20}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, WakingFirst) {
+  AddWaking(Ts{10}, thread_a_);
+
+  ForwardSchedTo(Ts{20});
+  AddSched(Ts{30}, thread_a_, "S");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{10}, Ts{20}, thread_a_, "R");
+  VerifyThreadState(Ts{20}, Ts{30}, thread_a_, kRunning);
+  VerifyThreadState(Ts{30}, base::nullopt, thread_a_, "S");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, SchedWithWaking) {
+  ForwardSchedTo(Ts{0});
+  AddSched(Ts{10}, thread_a_, "S");
+
+  AddWaking(Ts{15}, thread_a_);
+
+  ForwardSchedTo(Ts{20});
+  AddSched(Ts{25}, thread_a_, "R");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{0}, Ts{10}, thread_a_, kRunning);
+  VerifyThreadState(Ts{10}, Ts{15}, thread_a_, "S");
+  VerifyThreadState(Ts{15}, Ts{20}, thread_a_, "R");
+  VerifyThreadState(Ts{20}, Ts{25}, thread_a_, kRunning);
+  VerifyThreadState(Ts{25}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, SchedWithWakeup) {
+  ForwardSchedTo(Ts{0});
+  AddSched(Ts{10}, thread_a_, "S");
+
+  AddWakup(Ts{15}, thread_a_);
+
+  ForwardSchedTo(Ts{20});
+  AddSched(Ts{25}, thread_a_, "R");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{0}, Ts{10}, thread_a_, kRunning);
+  VerifyThreadState(Ts{10}, Ts{15}, thread_a_, "S");
+  VerifyThreadState(Ts{15}, Ts{20}, thread_a_, "R");
+  VerifyThreadState(Ts{20}, Ts{25}, thread_a_, kRunning);
+  VerifyThreadState(Ts{25}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, SchedIdleIgnored) {
+  ForwardSchedTo(Ts{0});
+  AddSched(Ts{10}, idle_thread_, "R");
+  AddSched(Ts{15}, thread_a_, "R");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{10}, Ts{15}, thread_a_, kRunning);
+  VerifyThreadState(Ts{15}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, NegativeSchedDuration) {
+  ForwardSchedTo(Ts{0});
+
+  AddSched(Ts{10}, thread_a_, "S");
+
+  AddWaking(Ts{15}, thread_a_);
+
+  ForwardSchedTo(Ts{20});
+  AddSched(base::nullopt, thread_a_, "");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{0}, Ts{10}, thread_a_, kRunning);
+  VerifyThreadState(Ts{10}, Ts{15}, thread_a_, "S");
+  VerifyThreadState(Ts{15}, Ts{20}, thread_a_, "R");
+  VerifyThreadState(Ts{20}, base::nullopt, thread_a_, kRunning);
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, WakingOnRunningThreadAtEnd) {
+  AddWaking(Ts{5}, thread_a_);
+
+  ForwardSchedTo(Ts{10});
+  AddSched(base::nullopt, thread_a_, "");
+
+  AddWaking(Ts{15}, thread_a_);
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{5}, Ts{10}, thread_a_, "R");
+  VerifyThreadState(Ts{10}, base::nullopt, thread_a_, kRunning);
+  VerifyThreadState(Ts{15}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, SchedDataLoss) {
+  ForwardSchedTo(Ts{10});
+  AddSched(base::nullopt, thread_a_, "");
+  ForwardSchedTo(Ts{30});
+  AddSched(Ts{40}, thread_a_, "D");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{10}, base::nullopt, thread_a_, kRunning);
+  VerifyThreadState(Ts{30}, Ts{40}, thread_a_, kRunning);
+  VerifyThreadState(Ts{40}, base::nullopt, thread_a_, "D");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, StrechedSchedIgnored) {
+  ForwardSchedTo(Ts{10});
+  AddSched(Ts{100}, thread_a_, "");
+
+  RunThreadStateComputation(Ts{100});
+
+  VerifyThreadState(Ts{10}, base::nullopt, thread_a_, kRunning);
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, WakingAfterStrechedSched) {
+  ForwardSchedTo(Ts{10});
+  AddSched(Ts{100}, thread_a_, "");
+
+  AddWaking(Ts{15}, thread_a_);
+
+  RunThreadStateComputation(Ts{100});
+
+  VerifyThreadState(Ts{10}, base::nullopt, thread_a_, kRunning);
+  VerifyThreadState(Ts{15}, base::nullopt, thread_a_, "R");
+
+  VerifyEndOfThreadState();
+}
+
+TEST_F(ThreadStateGeneratorUnittest, BlockedReason) {
+  ForwardSchedTo(Ts{10});
+  AddSched(Ts{12}, thread_a_, "D");
+  AddWaking(Ts{15}, thread_a_);
+  AddBlockedReason(Ts{16}, thread_a_, true);
+
+  ForwardSchedTo(Ts{18});
+  AddSched(Ts{20}, thread_a_, "S");
+  AddWaking(Ts{24}, thread_a_);
+  AddBlockedReason(Ts{26}, thread_a_, false);
+
+  ForwardSchedTo(Ts{29});
+  AddSched(Ts{30}, thread_a_, "R");
+
+  ForwardSchedTo(Ts{39});
+  AddSched(Ts{40}, thread_a_, "D");
+  AddBlockedReason(Ts{44}, thread_a_, false);
+
+  ForwardSchedTo(Ts{49});
+  AddSched(Ts{50}, thread_a_, "D");
+
+  RunThreadStateComputation();
+
+  VerifyThreadState(Ts{10}, Ts{12}, thread_a_, kRunning);
+  VerifyThreadState(Ts{12}, Ts{15}, thread_a_, "D", true);
+  VerifyThreadState(Ts{15}, Ts{18}, thread_a_, "R");
+
+  VerifyThreadState(Ts{18}, Ts{20}, thread_a_, kRunning);
+  VerifyThreadState(Ts{20}, Ts{24}, thread_a_, "S", false);
+  VerifyThreadState(Ts{24}, Ts{29}, thread_a_, "R");
+
+  VerifyThreadState(Ts{29}, Ts{30}, thread_a_, kRunning);
+  VerifyThreadState(Ts{30}, Ts{39}, thread_a_, "R", base::nullopt);
+
+  VerifyThreadState(Ts{39}, Ts{40}, thread_a_, kRunning);
+  VerifyThreadState(Ts{40}, Ts{49}, thread_a_, "D", false);
+
+  VerifyThreadState(Ts{49}, Ts{50}, thread_a_, kRunning);
+  VerifyThreadState(Ts{50}, base::nullopt, thread_a_, "D");
+
+  VerifyEndOfThreadState();
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 7821d3a..cca47fa 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -83,48 +83,12 @@
 const char kLegacyEventGlobalIdKey[] = "global_id";
 const char kLegacyEventLocalIdKey[] = "local_id";
 const char kLegacyEventIdScopeKey[] = "id_scope";
-const char kLegacyEventBindIdKey[] = "bind_id";
-const char kLegacyEventBindToEnclosingKey[] = "bind_to_enclosing";
-const char kLegacyEventFlowDirectionKey[] = "flow_direction";
-const char kFlowDirectionValueIn[] = "in";
-const char kFlowDirectionValueOut[] = "out";
-const char kFlowDirectionValueInout[] = "inout";
 const char kStrippedArgument[] = "__stripped__";
 
 const char* GetNonNullString(const TraceStorage* storage, StringId id) {
   return id == kNullStringId ? "" : storage->GetString(id).c_str();
 }
 
-std::string PrintUint64(uint64_t x) {
-  char hex_str[19];
-  sprintf(hex_str, "0x%" PRIx64, x);
-  return hex_str;
-}
-
-void ConvertLegacyFlowEventArgs(const Json::Value& legacy_args,
-                                Json::Value* event) {
-  if (legacy_args.isMember(kLegacyEventBindIdKey)) {
-    (*event)["bind_id"] =
-        PrintUint64(legacy_args[kLegacyEventBindIdKey].asUInt64());
-  }
-
-  if (legacy_args.isMember(kLegacyEventBindToEnclosingKey))
-    (*event)["bp"] = "e";
-
-  if (legacy_args.isMember(kLegacyEventFlowDirectionKey)) {
-    const char* val = legacy_args[kLegacyEventFlowDirectionKey].asCString();
-    if (strcmp(val, kFlowDirectionValueIn) == 0) {
-      (*event)["flow_in"] = true;
-    } else if (strcmp(val, kFlowDirectionValueOut) == 0) {
-      (*event)["flow_out"] = true;
-    } else {
-      PERFETTO_DCHECK(strcmp(val, kFlowDirectionValueInout) == 0);
-      (*event)["flow_in"] = true;
-      (*event)["flow_out"] = true;
-    }
-  }
-}
-
 class JsonExporter {
  public:
   JsonExporter(const TraceStorage* storage,
@@ -153,6 +117,10 @@
     if (!status.ok())
       return status;
 
+    status = ExportFlows();
+    if (!status.ok())
+      return status;
+
     status = ExportRawEvents();
     if (!status.ok())
       return status;
@@ -169,6 +137,10 @@
     if (!status.ok())
       return status;
 
+    status = ExportMemorySnapshots();
+    if (!status.ok())
+      return status;
+
     return util::OkStatus();
   }
 
@@ -581,7 +553,7 @@
             return variadic.real_value;
           }
         case Variadic::kPointer:
-          return PrintUint64(variadic.pointer_value);
+          return base::Uint64ToHexString(variadic.pointer_value);
         case Variadic::kBool:
           return variadic.bool_value;
         case Variadic::kJson:
@@ -765,7 +737,6 @@
       event["args"] =
           args_builder_.GetArgs(slices.arg_set_id()[i]);  // Makes a copy.
       if (event["args"].isMember(kLegacyEventArgsKey)) {
-        ConvertLegacyFlowEventArgs(event["args"][kLegacyEventArgsKey], &event);
 
         if (event["args"][kLegacyEventArgsKey].isMember(
                 kLegacyEventPassthroughUtidKey)) {
@@ -793,7 +764,8 @@
       if (track_args_id) {
         track_args = &args_builder_.GetArgs(*track_args_id);
         legacy_chrome_track = (*track_args)["source"].asString() == "chrome";
-        is_child_track = track_args->isMember("parent_track_id");
+        is_child_track = track_args->isMember("is_root_in_scope") &&
+                         !(*track_args)["is_root_in_scope"].asBool();
       }
 
       const auto& thread_track = storage_->thread_track_table();
@@ -905,13 +877,13 @@
           bool source_id_is_process_scoped =
               (*track_args)["source_id_is_process_scoped"].asBool();
           if (source_id_is_process_scoped) {
-            event["id2"]["local"] = PrintUint64(source_id);
+            event["id2"]["local"] = base::Uint64ToHexString(source_id);
           } else {
             // Some legacy importers don't understand "id2" fields, so we use
             // the "usually" global "id" field instead. This works as long as
             // the event phase is not in {'N', 'D', 'O', '(', ')'}, see
             // "LOCAL_ID_PHASES" in catapult.
-            event["id"] = PrintUint64(source_id);
+            event["id"] = base::Uint64ToHexString(source_id);
           }
         } else {
           if (opt_thread_track_row) {
@@ -919,7 +891,7 @@
             auto pid_and_tid = UtidToPidAndTid(utid);
             event["pid"] = Json::Int(pid_and_tid.first);
             event["tid"] = Json::Int(pid_and_tid.second);
-            event["id2"]["local"] = PrintUint64(track_id.value);
+            event["id2"]["local"] = base::Uint64ToHexString(track_id.value);
           } else if (opt_process_row) {
             uint32_t upid = process_track.upid()[*opt_process_row];
             uint32_t exported_pid = UpidToPid(upid);
@@ -927,7 +899,7 @@
             event["tid"] =
                 Json::Int(legacy_utid ? UtidToPidAndTid(*legacy_utid).second
                                       : exported_pid);
-            event["id2"]["local"] = PrintUint64(track_id.value);
+            event["id2"]["local"] = base::Uint64ToHexString(track_id.value);
           } else {
             if (legacy_utid) {
               auto pid_and_tid = UtidToPidAndTid(*legacy_utid);
@@ -939,7 +911,7 @@
             // the "usually" global "id" field instead. This works as long as
             // the event phase is not in {'N', 'D', 'O', '(', ')'}, see
             // "LOCAL_ID_PHASES" in catapult.
-            event["id"] = PrintUint64(track_id.value);
+            event["id"] = base::Uint64ToHexString(track_id.value);
           }
         }
 
@@ -1007,6 +979,83 @@
     return util::OkStatus();
   }
 
+  base::Optional<Json::Value> CreateFlowEventV1(uint32_t flow_id,
+                                                SliceId slice_id,
+                                                std::string name,
+                                                std::string cat,
+                                                Json::Value args,
+                                                bool flow_begin) {
+    const auto& slices = storage_->slice_table();
+    const auto& thread_tracks = storage_->thread_track_table();
+
+    auto opt_slice_idx = slices.id().IndexOf(slice_id);
+    if (!opt_slice_idx)
+      return base::nullopt;
+    uint32_t slice_idx = opt_slice_idx.value();
+
+    TrackId track_id = storage_->slice_table().track_id()[slice_idx];
+    auto opt_thread_track_idx = thread_tracks.id().IndexOf(track_id);
+    // catapult only supports flow events attached to thread-track slices
+    if (!opt_thread_track_idx)
+      return base::nullopt;
+
+    UniqueTid utid = thread_tracks.utid()[opt_thread_track_idx.value()];
+    auto pid_and_tid = UtidToPidAndTid(utid);
+    Json::Value event;
+    event["id"] = flow_id;
+    event["pid"] = Json::Int(pid_and_tid.first);
+    event["tid"] = Json::Int(pid_and_tid.second);
+    event["cat"] = cat;
+    event["name"] = name;
+    event["ph"] = (flow_begin ? "s" : "f");
+    event["ts"] = Json::Int64(slices.ts()[slice_idx] / 1000);
+    if (!flow_begin) {
+      event["bp"] = "e";
+    }
+    event["args"] = std::move(args);
+    return std::move(event);
+  }
+
+  util::Status ExportFlows() {
+    const auto& flow_table = storage_->flow_table();
+    const auto& slice_table = storage_->slice_table();
+    for (uint32_t i = 0; i < flow_table.row_count(); i++) {
+      SliceId slice_out = flow_table.slice_out()[i];
+      SliceId slice_in = flow_table.slice_in()[i];
+      uint32_t arg_set_id = flow_table.arg_set_id()[i];
+
+      std::string cat;
+      std::string name;
+      auto args = args_builder_.GetArgs(arg_set_id);
+      if (arg_set_id != kInvalidArgSetId) {
+        cat = args["cat"].asString();
+        name = args["name"].asString();
+        // Don't export these args since they are only used for this export and
+        // weren't part of the original event.
+        args.removeMember("name");
+        args.removeMember("cat");
+      } else {
+        auto opt_slice_out_idx = slice_table.id().IndexOf(slice_out);
+        PERFETTO_DCHECK(opt_slice_out_idx.has_value());
+        StringId cat_id = slice_table.category()[opt_slice_out_idx.value()];
+        StringId name_id = slice_table.name()[opt_slice_out_idx.value()];
+        cat = GetNonNullString(storage_, cat_id);
+        name = GetNonNullString(storage_, name_id);
+      }
+
+      auto out_event = CreateFlowEventV1(i, slice_out, name, cat, args,
+                                         /* flow_begin = */ true);
+      auto in_event = CreateFlowEventV1(i, slice_in, name, cat, std::move(args),
+                                        /* flow_begin = */ false);
+
+      if (out_event && in_event) {
+        writer_.WriteCommonEvent(out_event.value());
+        writer_.WriteCommonEvent(in_event.value());
+      }
+    }
+    return util::OkStatus();
+  }
+
   Json::Value ConvertLegacyRawEventToJson(uint32_t index) {
     const auto& events = storage_->raw_table();
 
@@ -1062,25 +1111,23 @@
       event["use_async_tts"] = legacy_args[kLegacyEventUseAsyncTtsKey];
 
     if (legacy_args.isMember(kLegacyEventUnscopedIdKey)) {
-      event["id"] =
-          PrintUint64(legacy_args[kLegacyEventUnscopedIdKey].asUInt64());
+      event["id"] = base::Uint64ToHexString(
+          legacy_args[kLegacyEventUnscopedIdKey].asUInt64());
     }
 
     if (legacy_args.isMember(kLegacyEventGlobalIdKey)) {
-      event["id2"]["global"] =
-          PrintUint64(legacy_args[kLegacyEventGlobalIdKey].asUInt64());
+      event["id2"]["global"] = base::Uint64ToHexString(
+          legacy_args[kLegacyEventGlobalIdKey].asUInt64());
     }
 
     if (legacy_args.isMember(kLegacyEventLocalIdKey)) {
-      event["id2"]["local"] =
-          PrintUint64(legacy_args[kLegacyEventLocalIdKey].asUInt64());
+      event["id2"]["local"] = base::Uint64ToHexString(
+          legacy_args[kLegacyEventLocalIdKey].asUInt64());
     }
 
     if (legacy_args.isMember(kLegacyEventIdScopeKey))
       event["scope"] = legacy_args[kLegacyEventIdScopeKey];
 
-    ConvertLegacyFlowEventArgs(legacy_args, &event);
-
     event["args"].removeMember(kLegacyEventArgsKey);
 
     return event;
@@ -1148,7 +1195,7 @@
       // want the samples to show up on their own track in the trace-viewer but
       // not nested together.
       static size_t g_id_counter = 0;
-      event["id"] = PrintUint64(++g_id_counter);
+      event["id"] = base::Uint64ToHexString(++g_id_counter);
 
       const auto& callsites = storage_->stack_profile_callsite_table();
       const auto& frames = storage_->stack_profile_frame_table();
@@ -1177,7 +1224,7 @@
         char frame_entry[1024];
         snprintf(frame_entry, sizeof(frame_entry), "%s - %s [%s]\n",
                  (symbol_name.empty()
-                      ? PrintUint64(
+                      ? base::Uint64ToHexString(
                             static_cast<uint64_t>(frames.rel_pc()[frame_row]))
                             .c_str()
                       : symbol_name.c_str()),
@@ -1293,6 +1340,218 @@
     return util::OkStatus();
   }
 
+  util::Status ExportMemorySnapshots() {
+    const auto& memory_snapshots = storage_->memory_snapshot_table();
+    base::Optional<StringId> private_footprint_id =
+        storage_->string_pool().GetId("chrome.private_footprint_kb");
+    base::Optional<StringId> peak_resident_set_id =
+        storage_->string_pool().GetId("chrome.peak_resident_set_kb");
+
+    for (uint32_t memory_index = 0; memory_index < memory_snapshots.row_count();
+         ++memory_index) {
+      Json::Value event_base;
+
+      event_base["ph"] = "v";
+      event_base["cat"] = "disabled-by-default-memory-infra";
+      auto snapshot_id = memory_snapshots.id()[memory_index].value;
+      event_base["id"] = base::Uint64ToHexString(snapshot_id);
+      int64_t snapshot_ts = memory_snapshots.timestamp()[memory_index];
+      event_base["ts"] = Json::Int64(snapshot_ts / 1000);
+      // TODO(crbug:1116359): Add dump type to the snapshot proto
+      // to properly fill event_base["name"]
+      event_base["name"] = "periodic_interval";
+      event_base["args"]["dumps"]["level_of_detail"] = GetNonNullString(
+          storage_, memory_snapshots.detail_level()[memory_index]);
+
+      // Export OS dump events for processes with relevant data.
+      const auto& process_table = storage_->process_table();
+      for (UniquePid upid = 0; upid < process_table.row_count(); ++upid) {
+        Json::Value event =
+            FillInProcessEventDetails(event_base, process_table.pid()[upid]);
+        Json::Value& totals = event["args"]["dumps"]["process_totals"];
+
+        const auto& process_counters = storage_->process_counter_track_table();
+
+        for (uint32_t counter_index = 0;
+             counter_index < process_counters.row_count(); ++counter_index) {
+          if (process_counters.upid()[counter_index] != upid)
+            continue;
+          TrackId track_id = process_counters.id()[counter_index];
+          if (private_footprint_id && (process_counters.name()[counter_index] ==
+                                       private_footprint_id)) {
+            totals["private_footprint_bytes"] = base::Uint64ToHexStringNoPrefix(
+                GetCounterValue(track_id, snapshot_ts));
+          } else if (peak_resident_set_id &&
+                     (process_counters.name()[counter_index] ==
+                      peak_resident_set_id)) {
+            totals["peak_resident_set_size"] = base::Uint64ToHexStringNoPrefix(
+                GetCounterValue(track_id, snapshot_ts));
+          }
+        }
+
+        auto process_args_id = process_table.arg_set_id()[upid];
+        if (process_args_id) {
+          const Json::Value* process_args =
+              &args_builder_.GetArgs(process_args_id);
+          if (process_args->isMember("is_peak_rss_resettable")) {
+            totals["is_peak_rss_resettable"] =
+                (*process_args)["is_peak_rss_resettable"];
+          }
+        }
+
+        Json::Value& smaps =
+            event["args"]["dumps"]["process_mmaps"]["vm_regions"];
+        const auto& smaps_table = storage_->profiler_smaps_table();
+        for (uint32_t smaps_index = 0; smaps_index < smaps_table.row_count();
+             ++smaps_index) {
+          if (smaps_table.upid()[smaps_index] != upid)
+            continue;
+          if (smaps_table.ts()[smaps_index] != snapshot_ts)
+            continue;
+          Json::Value region;
+          region["mf"] =
+              GetNonNullString(storage_, smaps_table.file_name()[smaps_index]);
+          region["pf"] =
+              Json::Int64(smaps_table.protection_flags()[smaps_index]);
+          region["sa"] = base::Uint64ToHexStringNoPrefix(
+              static_cast<uint64_t>(smaps_table.start_address()[smaps_index]));
+          region["sz"] = base::Uint64ToHexStringNoPrefix(
+              static_cast<uint64_t>(smaps_table.size_kb()[smaps_index]));
+          region["ts"] =
+              Json::Int64(smaps_table.module_timestamp()[smaps_index]);
+          region["id"] = GetNonNullString(
+              storage_, smaps_table.module_debugid()[smaps_index]);
+          region["df"] = GetNonNullString(
+              storage_, smaps_table.module_debug_path()[smaps_index]);
+          region["bs"]["pc"] =
+              base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(
+                  smaps_table.private_clean_resident_kb()[smaps_index]));
+          region["bs"]["pd"] =
+              base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(
+                  smaps_table.private_dirty_kb()[smaps_index]));
+          region["bs"]["pss"] =
+              base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(
+                  smaps_table.proportional_resident_kb()[smaps_index]));
+          region["bs"]["sc"] =
+              base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(
+                  smaps_table.shared_clean_resident_kb()[smaps_index]));
+          region["bs"]["sd"] =
+              base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(
+                  smaps_table.shared_dirty_resident_kb()[smaps_index]));
+          region["bs"]["sw"] = base::Uint64ToHexStringNoPrefix(
+              static_cast<uint64_t>(smaps_table.swap_kb()[smaps_index]));
+          smaps.append(region);
+        }
+
+        if (!totals.empty() || !smaps.empty())
+          writer_.WriteCommonEvent(event);
+      }
+
+      // Export chrome dump events for process snapshots in current memory
+      // snapshot.
+      const auto& process_snapshots = storage_->process_memory_snapshot_table();
+
+      for (uint32_t process_index = 0;
+           process_index < process_snapshots.row_count(); ++process_index) {
+        if (process_snapshots.snapshot_id()[process_index].value != snapshot_id)
+          continue;
+
+        auto process_snapshot_id = process_snapshots.id()[process_index].value;
+        uint32_t pid = UpidToPid(process_snapshots.upid()[process_index]);
+
+        // Shared memory nodes are imported into a fake process with pid 0.
+        // Catapult expects them to be associated with one of the real processes
+        // of the snapshot, so we choose the first one we can find and replace
+        // the pid.
+        if (pid == 0) {
+          for (uint32_t i = 0; i < process_snapshots.row_count(); ++i) {
+            if (process_snapshots.snapshot_id()[i].value != snapshot_id)
+              continue;
+            uint32_t new_pid = UpidToPid(process_snapshots.upid()[i]);
+            if (new_pid != 0) {
+              pid = new_pid;
+              break;
+            }
+          }
+        }
+
+        Json::Value event = FillInProcessEventDetails(event_base, pid);
+
+        const auto& snapshot_nodes = storage_->memory_snapshot_node_table();
+
+        for (uint32_t node_index = 0; node_index < snapshot_nodes.row_count();
+             ++node_index) {
+          if (snapshot_nodes.process_snapshot_id()[node_index].value !=
+              process_snapshot_id) {
+            continue;
+          }
+          const char* path =
+              GetNonNullString(storage_, snapshot_nodes.path()[node_index]);
+          event["args"]["dumps"]["allocators"][path]["guid"] =
+              base::Uint64ToHexStringNoPrefix(
+                  static_cast<uint64_t>(snapshot_nodes.id()[node_index].value));
+          if (snapshot_nodes.size()[node_index]) {
+            AddAttributeToMemoryNode(&event, path, "size",
+                                     snapshot_nodes.size()[node_index],
+                                     "bytes");
+          }
+          if (snapshot_nodes.effective_size()[node_index]) {
+            AddAttributeToMemoryNode(
+                &event, path, "effective_size",
+                snapshot_nodes.effective_size()[node_index], "bytes");
+          }
+
+          auto node_args_id = snapshot_nodes.arg_set_id()[node_index];
+          if (!node_args_id)
+            continue;
+          const Json::Value* node_args =
+              &args_builder_.GetArgs(node_args_id.value());
+          for (const auto& arg_name : node_args->getMemberNames()) {
+            const Json::Value& arg_value = (*node_args)[arg_name]["value"];
+            if (arg_value.empty())
+              continue;
+            if (arg_value.isString()) {
+              AddAttributeToMemoryNode(&event, path, arg_name,
+                                       arg_value.asString());
+            } else if (arg_value.isInt64()) {
+              Json::Value unit = (*node_args)[arg_name]["unit"];
+              if (unit.empty())
+                unit = "unknown";
+              AddAttributeToMemoryNode(&event, path, arg_name,
+                                       arg_value.asInt64(), unit.asString());
+            }
+          }
+        }
+
+        const auto& snapshot_edges = storage_->memory_snapshot_edge_table();
+
+        for (uint32_t edge_index = 0; edge_index < snapshot_edges.row_count();
+             ++edge_index) {
+          SnapshotNodeId source_node_id =
+              snapshot_edges.source_node_id()[edge_index];
+          uint32_t source_node_row =
+              *snapshot_nodes.id().IndexOf(source_node_id);
+
+          if (snapshot_nodes.process_snapshot_id()[source_node_row].value !=
+              process_snapshot_id) {
+            continue;
+          }
+          Json::Value edge;
+          edge["source"] = base::Uint64ToHexStringNoPrefix(
+              snapshot_edges.source_node_id()[edge_index].value);
+          edge["target"] = base::Uint64ToHexStringNoPrefix(
+              snapshot_edges.target_node_id()[edge_index].value);
+          edge["importance"] =
+              Json::Int(snapshot_edges.importance()[edge_index]);
+          edge["type"] = "ownership";
+          event["args"]["dumps"]["allocators_graph"].append(edge);
+        }
+        writer_.WriteCommonEvent(event);
+      }
+    }
+    return util::OkStatus();
+  }
+
   uint32_t UpidToPid(UniquePid upid) {
     auto pid_it = upids_to_exported_pids_.find(upid);
     PERFETTO_DCHECK(pid_it != upids_to_exported_pids_.end());
@@ -1329,6 +1588,62 @@
     return false;
   }
 
+  Json::Value FillInProcessEventDetails(const Json::Value& event,
+                                        uint32_t pid) {
+    Json::Value output = event;
+    output["pid"] = Json::Int(pid);
+    output["tid"] = Json::Int(-1);
+    return output;
+  }
+
+  void AddAttributeToMemoryNode(Json::Value* event,
+                                const std::string& path,
+                                const std::string& key,
+                                int64_t value,
+                                const std::string& units) {
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
+        base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(value));
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
+        "scalar";
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["units"] =
+        units;
+  }
+
+  void AddAttributeToMemoryNode(Json::Value* event,
+                                const std::string& path,
+                                const std::string& key,
+                                const std::string& value,
+                                const std::string& units = "") {
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
+        value;
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
+        "string";
+    (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["units"] =
+        units;
+  }
+
+  uint64_t GetCounterValue(TrackId track_id, int64_t ts) {
+    const auto& counter_table = storage_->counter_table();
+    auto begin = counter_table.ts().begin();
+    auto end = counter_table.ts().end();
+    PERFETTO_DCHECK(counter_table.ts().IsSorted() &&
+                    counter_table.ts().IsColumnType<int64_t>());
+    // The timestamp column is sorted, so we can binary search for a matching
+    // timestamp. Note that we don't use RowMap operations like FilterInto()
+    // here because they bloat trace processor's binary size in Chrome too much.
+    auto it = std::lower_bound(begin, end, ts,
+                               [](const SqlValue& value, int64_t expected_ts) {
+                                 return value.AsLong() < expected_ts;
+                               });
+    for (; it < end; ++it) {
+      if ((*it).AsLong() != ts)
+        break;
+      if (counter_table.track_id()[it.row()].value == track_id.value)
+        return static_cast<uint64_t>(counter_table.value()[it.row()]);
+    }
+    return 0;
+  }
+
   const TraceStorage* storage_;
   ArgsBuilder args_builder_;
   TraceFormatWriter writer_;
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 6c5578c..1dba4d2 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -24,11 +24,14 @@
 #include <json/reader.h>
 #include <json/value.h>
 
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"
+#include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 #include "test/gtest_and_gmock.h"
@@ -68,6 +71,7 @@
   ExportJsonTest() {
     context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
     context_.args_tracker.reset(new ArgsTracker(&context_));
+    context_.event_tracker.reset(new EventTracker(&context_));
     context_.storage.reset(new TraceStorage());
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.metadata_tracker.reset(new MetadataTracker(&context_));
@@ -227,9 +231,8 @@
 }
 
 TEST_F(ExportJsonTest, SystemEventsIgnored) {
-  constexpr int64_t kCookie = 22;
-  TrackId track = context_.track_tracker->InternAndroidAsyncTrack(
-      /*name=*/kNullStringId, /*upid=*/0, kCookie);
+  TrackId track = context_.track_tracker->CreateAndroidAsyncTrack(
+      /*name=*/kNullStringId, /*upid=*/0);
   context_.args_tracker->Flush();  // Flush track args.
 
   // System events have no category.
@@ -446,10 +449,6 @@
 TEST_F(ExportJsonTest, StorageWithSliceAndFlowEventArgs) {
   const char* kCategory = "cat";
   const char* kName = "name";
-  const uint64_t kBindId = 0xaa00aa00aa00aa00;
-  const char* kFlowDirection = "inout";
-  const char* kArgName = "arg_name";
-  const int kArgValue = 123;
 
   TraceStorage* storage = context_.storage.get();
 
@@ -458,23 +457,14 @@
   context_.args_tracker->Flush();  // Flush track args.
   StringId cat_id = storage->InternString(base::StringView(kCategory));
   StringId name_id = storage->InternString(base::StringView(kName));
-  SliceId id = storage->mutable_slice_table()
-                   ->Insert({0, 0, track, cat_id, name_id, 0, 0, 0})
-                   .id;
-  auto inserter = context_.args_tracker->AddArgsTo(id);
+  SliceId id1 = storage->mutable_slice_table()
+                    ->Insert({0, 0, track, cat_id, name_id, 0, 0, 0})
+                    .id;
+  SliceId id2 = storage->mutable_slice_table()
+                    ->Insert({100, 0, track, cat_id, name_id, 0, 0, 0})
+                    .id;
 
-  auto add_arg = [&](const char* key, Variadic value) {
-    inserter.AddArg(storage->InternString(key), value);
-  };
-
-  add_arg("legacy_event.bind_id", Variadic::UnsignedInteger(kBindId));
-  add_arg("legacy_event.bind_to_enclosing", Variadic::Boolean(true));
-  StringId flow_direction_id = storage->InternString(kFlowDirection);
-  add_arg("legacy_event.flow_direction", Variadic::String(flow_direction_id));
-
-  add_arg(kArgName, Variadic::Integer(kArgValue));
-
-  context_.args_tracker->Flush();
+  storage->mutable_flow_table()->Insert({id1, id2, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
   FILE* output = fopen(temp_file.path().c_str(), "w+");
@@ -483,17 +473,30 @@
   EXPECT_TRUE(status.ok());
 
   Json::Value result = ToJsonValue(ReadFile(output));
-  EXPECT_EQ(result["traceEvents"].size(), 1u);
+  EXPECT_EQ(result["traceEvents"].size(), 4u);
 
-  Json::Value event = result["traceEvents"][0];
-  EXPECT_EQ(event["cat"].asString(), kCategory);
-  EXPECT_EQ(event["name"].asString(), kName);
-  EXPECT_EQ(event["bind_id"].asString(), "0xaa00aa00aa00aa00");
-  EXPECT_EQ(event["bp"].asString(), "e");
-  EXPECT_EQ(event["flow_in"].asBool(), true);
-  EXPECT_EQ(event["flow_out"].asBool(), true);
-  EXPECT_EQ(event["args"][kArgName].asInt(), kArgValue);
-  EXPECT_FALSE(event["args"].isMember("legacy_event"));
+  Json::Value slice_out = result["traceEvents"][0];
+  Json::Value slice_in = result["traceEvents"][1];
+  Json::Value flow_out = result["traceEvents"][2];
+  Json::Value flow_in = result["traceEvents"][3];
+
+  EXPECT_EQ(flow_out["cat"].asString(), kCategory);
+  EXPECT_EQ(flow_out["name"].asString(), kName);
+  EXPECT_EQ(flow_out["ph"].asString(), "s");
+  EXPECT_EQ(flow_out["tid"].asString(), slice_out["tid"].asString());
+  EXPECT_EQ(flow_out["pid"].asString(), slice_out["pid"].asString());
+
+  EXPECT_EQ(flow_in["cat"].asString(), kCategory);
+  EXPECT_EQ(flow_in["name"].asString(), kName);
+  EXPECT_EQ(flow_in["ph"].asString(), "f");
+  EXPECT_EQ(flow_in["bp"].asString(), "e");
+  EXPECT_EQ(flow_in["tid"].asString(), slice_in["tid"].asString());
+  EXPECT_EQ(flow_in["pid"].asString(), slice_in["pid"].asString());
+
+  EXPECT_LE(slice_out["ts"].asInt64(), flow_out["ts"].asInt64());
+  EXPECT_GE(slice_in["ts"].asInt64(), flow_in["ts"].asInt64());
+
+  EXPECT_EQ(flow_out["id"].asString(), flow_in["id"].asString());
 }
 
 TEST_F(ExportJsonTest, StorageWithListArgs) {
@@ -737,14 +740,15 @@
       {kTimestamp, 0, track, cat_id, name_id, 0, 0, 0});
 
   // Global track.
-  TrackId track2 = context_.track_tracker->GetOrCreateDefaultDescriptorTrack();
+  TrackEventTracker track_event_tracker(&context_);
+  TrackId track2 = track_event_tracker.GetOrCreateDefaultDescriptorTrack();
   context_.args_tracker->Flush();  // Flush track args.
   context_.storage->mutable_slice_table()->Insert(
       {kTimestamp2, 0, track2, cat_id, name_id, 0, 0, 0});
 
   // Async event track.
-  context_.track_tracker->ReserveDescriptorChildTrack(1234, 0, kNullStringId);
-  TrackId track3 = *context_.track_tracker->GetDescriptorTrack(1234);
+  track_event_tracker.ReserveDescriptorChildTrack(1234, 0, kNullStringId);
+  TrackId track3 = *track_event_tracker.GetDescriptorTrack(1234);
   context_.args_tracker->Flush();  // Flush track args.
   context_.storage->mutable_slice_table()->Insert(
       {kTimestamp3, 0, track3, cat_id, name_id, 0, 0, 0});
@@ -1287,10 +1291,6 @@
   EXPECT_EQ(event["use_async_tts"].asInt(), 1);
   EXPECT_EQ(event["id2"]["global"].asString(), "0xaaffaaffaaffaaff");
   EXPECT_EQ(event["scope"].asString(), kIdScope);
-  EXPECT_EQ(event["bind_id"].asString(), "0xaa00aa00aa00aa00");
-  EXPECT_EQ(event["bp"].asString(), "e");
-  EXPECT_EQ(event["flow_in"].asBool(), true);
-  EXPECT_EQ(event["flow_out"].asBool(), true);
   EXPECT_EQ(event["args"][kArgName].asInt(), kArgValue);
 }
 
@@ -1561,6 +1561,258 @@
   EXPECT_EQ(result[1]["name"].asString(), kName);
 }
 
+TEST_F(ExportJsonTest, MemorySnapshotOsDumpEvent) {
+  const int64_t kTimestamp = 10000000;
+  const int64_t kPeakResidentSetSize = 100000;
+  const int64_t kPrivateFootprintBytes = 200000;
+  const int64_t kProtectionFlags = 1;
+  const int64_t kStartAddress = 1000000000;
+  const int64_t kSizeKb = 1000;
+  const int64_t kPrivateCleanResidentKb = 2000;
+  const int64_t kPrivateDirtyKb = 3000;
+  const int64_t kProportionalResidentKb = 4000;
+  const int64_t kSharedCleanResidentKb = 5000;
+  const int64_t kSharedDirtyResidentKb = 6000;
+  const int64_t kSwapKb = 7000;
+  const int64_t kModuleTimestamp = 20000000;
+  const uint32_t kProcessID = 100;
+  const bool kIsPeakRssResettable = true;
+  const char* kLevelOfDetail = "detailed";
+  const char* kFileName = "filename";
+  const char* kModuleDebugid = "debugid";
+  const char* kModuleDebugPath = "debugpath";
+
+  UniquePid upid = context_.process_tracker->GetOrCreateProcess(kProcessID);
+  TrackId track = context_.track_tracker->InternProcessTrack(upid);
+  StringId level_of_detail_id =
+      context_.storage->InternString(base::StringView(kLevelOfDetail));
+  auto snapshot_id = context_.storage->mutable_memory_snapshot_table()
+                         ->Insert({kTimestamp, track, level_of_detail_id})
+                         .id;
+
+  StringId peak_resident_set_size_id =
+      context_.storage->InternString("chrome.peak_resident_set_kb");
+  TrackId peak_resident_set_size_counter =
+      context_.track_tracker->InternProcessCounterTrack(
+          peak_resident_set_size_id, upid);
+  context_.event_tracker->PushCounter(kTimestamp, kPeakResidentSetSize,
+                                      peak_resident_set_size_counter);
+
+  StringId private_footprint_bytes_id =
+      context_.storage->InternString("chrome.private_footprint_kb");
+  TrackId private_footprint_bytes_counter =
+      context_.track_tracker->InternProcessCounterTrack(
+          private_footprint_bytes_id, upid);
+  context_.event_tracker->PushCounter(kTimestamp, kPrivateFootprintBytes,
+                                      private_footprint_bytes_counter);
+
+  StringId is_peak_rss_resettable_id =
+      context_.storage->InternString("is_peak_rss_resettable");
+  context_.args_tracker->AddArgsTo(upid).AddArg(
+      is_peak_rss_resettable_id, Variadic::Boolean(kIsPeakRssResettable));
+  context_.args_tracker->Flush();
+
+  context_.storage->mutable_profiler_smaps_table()->Insert(
+      {upid, kTimestamp, kNullStringId, kSizeKb, kPrivateDirtyKb, kSwapKb,
+       context_.storage->InternString(kFileName), kStartAddress,
+       kModuleTimestamp, context_.storage->InternString(kModuleDebugid),
+       context_.storage->InternString(kModuleDebugPath), kProtectionFlags,
+       kPrivateCleanResidentKb, kSharedDirtyResidentKb, kSharedCleanResidentKb,
+       0, kProportionalResidentKb});
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  util::Status status = ExportJson(context_.storage.get(), output);
+
+  EXPECT_TRUE(status.ok());
+
+  Json::Value result = ToJsonValue(ReadFile(output));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["ph"].asString(), "v");
+  EXPECT_EQ(event["cat"].asString(), "disabled-by-default-memory-infra");
+  EXPECT_EQ(event["id"].asString(), base::Uint64ToHexString(snapshot_id.value));
+  EXPECT_EQ(event["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(event["name"].asString(), "periodic_interval");
+  EXPECT_EQ(event["pid"].asUInt(), kProcessID);
+  EXPECT_EQ(event["tid"].asInt(), -1);
+
+  EXPECT_TRUE(event["args"].isObject());
+  EXPECT_EQ(event["args"]["dumps"]["level_of_detail"].asString(),
+            kLevelOfDetail);
+
+  EXPECT_EQ(event["args"]["dumps"]["process_totals"]["peak_resident_set_size"]
+                .asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kPeakResidentSetSize)));
+  EXPECT_EQ(event["args"]["dumps"]["process_totals"]["private_footprint_bytes"]
+                .asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kPrivateFootprintBytes)));
+  EXPECT_EQ(event["args"]["dumps"]["process_totals"]["is_peak_rss_resettable"]
+                .asBool(),
+            kIsPeakRssResettable);
+
+  EXPECT_TRUE(event["args"]["dumps"]["process_mmaps"]["vm_regions"].isArray());
+  EXPECT_EQ(event["args"]["dumps"]["process_mmaps"]["vm_regions"].size(), 1u);
+  Json::Value region = event["args"]["dumps"]["process_mmaps"]["vm_regions"][0];
+  EXPECT_EQ(region["mf"].asString(), kFileName);
+  EXPECT_EQ(region["pf"].asInt64(), kProtectionFlags);
+  EXPECT_EQ(region["sa"].asString(), base::Uint64ToHexStringNoPrefix(
+                                         static_cast<uint64_t>(kStartAddress)));
+  EXPECT_EQ(region["sz"].asString(),
+            base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kSizeKb)));
+  EXPECT_EQ(region["id"].asString(), kModuleDebugid);
+  EXPECT_EQ(region["df"].asString(), kModuleDebugPath);
+  EXPECT_EQ(region["bs"]["pc"].asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kPrivateCleanResidentKb)));
+  EXPECT_EQ(
+      region["bs"]["pd"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kPrivateDirtyKb)));
+  EXPECT_EQ(region["bs"]["pss"].asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kProportionalResidentKb)));
+  EXPECT_EQ(region["bs"]["sc"].asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kSharedCleanResidentKb)));
+  EXPECT_EQ(region["bs"]["sd"].asString(),
+            base::Uint64ToHexStringNoPrefix(
+                static_cast<uint64_t>(kSharedDirtyResidentKb)));
+  EXPECT_EQ(region["bs"]["sw"].asString(),
+            base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kSwapKb)));
+}
+
+TEST_F(ExportJsonTest, MemorySnapshotChromeDumpEvent) {
+  const int64_t kTimestamp = 10000000;
+  const int64_t kSize = 1000;
+  const int64_t kEffectiveSize = 2000;
+  const int64_t kScalarAttrValue = 3000;
+  const uint32_t kOsProcessID = 100;
+  const uint32_t kChromeProcessID = 200;
+  const uint32_t kImportance = 1;
+  const char* kLevelOfDetail = "detailed";
+  const char* kPath1 = "path/to_file1";
+  const char* kPath2 = "path/to_file2";
+  const char* kScalarAttrUnits = "scalar_units";
+  const char* kStringAttrValue = "string_value";
+  const std::string kScalarAttrName = "scalar_name";
+  const std::string kStringAttrName = "string_name";
+
+  UniquePid os_upid =
+      context_.process_tracker->GetOrCreateProcess(kOsProcessID);
+  TrackId track = context_.track_tracker->InternProcessTrack(os_upid);
+  StringId level_of_detail_id =
+      context_.storage->InternString(base::StringView(kLevelOfDetail));
+  auto snapshot_id = context_.storage->mutable_memory_snapshot_table()
+                         ->Insert({kTimestamp, track, level_of_detail_id})
+                         .id;
+
+  UniquePid chrome_upid =
+      context_.process_tracker->GetOrCreateProcess(kChromeProcessID);
+  auto process_id = context_.storage->mutable_process_memory_snapshot_table()
+                        ->Insert({snapshot_id, chrome_upid})
+                        .id;
+
+  StringId path1_id = context_.storage->InternString(base::StringView(kPath1));
+  StringId path2_id = context_.storage->InternString(base::StringView(kPath2));
+  SnapshotNodeId node1_id =
+      context_.storage->mutable_memory_snapshot_node_table()
+          ->Insert(
+              {process_id, SnapshotNodeId(0), path1_id, kSize, kEffectiveSize})
+          .id;
+  SnapshotNodeId node2_id =
+      context_.storage->mutable_memory_snapshot_node_table()
+          ->Insert({process_id, SnapshotNodeId(0), path2_id, 0, 0})
+          .id;
+
+  context_.args_tracker->AddArgsTo(node1_id).AddArg(
+      context_.storage->InternString(
+          base::StringView(kScalarAttrName + ".value")),
+      Variadic::Integer(kScalarAttrValue));
+  context_.args_tracker->AddArgsTo(node1_id).AddArg(
+      context_.storage->InternString(
+          base::StringView(kScalarAttrName + ".unit")),
+      Variadic::String(context_.storage->InternString(kScalarAttrUnits)));
+  context_.args_tracker->AddArgsTo(node1_id).AddArg(
+      context_.storage->InternString(
+          base::StringView(kStringAttrName + ".value")),
+      Variadic::String(context_.storage->InternString(kStringAttrValue)));
+  context_.args_tracker->Flush();
+
+  context_.storage->mutable_memory_snapshot_edge_table()->Insert(
+      {node1_id, node2_id, kImportance});
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  util::Status status = ExportJson(context_.storage.get(), output);
+
+  EXPECT_TRUE(status.ok());
+
+  Json::Value result = ToJsonValue(ReadFile(output));
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["ph"].asString(), "v");
+  EXPECT_EQ(event["cat"].asString(), "disabled-by-default-memory-infra");
+  EXPECT_EQ(event["id"].asString(), base::Uint64ToHexString(snapshot_id.value));
+  EXPECT_EQ(event["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(event["name"].asString(), "periodic_interval");
+  EXPECT_EQ(event["pid"].asUInt(), kChromeProcessID);
+  EXPECT_EQ(event["tid"].asInt(), -1);
+
+  EXPECT_TRUE(event["args"].isObject());
+  EXPECT_EQ(event["args"]["dumps"]["level_of_detail"].asString(),
+            kLevelOfDetail);
+
+  EXPECT_EQ(event["args"]["dumps"]["allocators"].size(), 2u);
+  Json::Value node1 = event["args"]["dumps"]["allocators"][kPath1];
+  EXPECT_TRUE(node1.isObject());
+  EXPECT_EQ(
+      node1["guid"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(node1_id.value)));
+  EXPECT_TRUE(node1["attrs"]["size"].isObject());
+  EXPECT_EQ(node1["attrs"]["size"]["value"].asString(),
+            base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kSize)));
+  EXPECT_EQ(node1["attrs"]["size"]["type"].asString(), "scalar");
+  EXPECT_EQ(node1["attrs"]["size"]["units"].asString(), "bytes");
+  EXPECT_EQ(
+      node1["attrs"]["effective_size"]["value"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kEffectiveSize)));
+  EXPECT_TRUE(node1["attrs"][kScalarAttrName].isObject());
+  EXPECT_EQ(
+      node1["attrs"][kScalarAttrName]["value"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(kScalarAttrValue)));
+  EXPECT_EQ(node1["attrs"][kScalarAttrName]["type"].asString(), "scalar");
+  EXPECT_EQ(node1["attrs"][kScalarAttrName]["units"].asString(),
+            kScalarAttrUnits);
+  EXPECT_TRUE(node1["attrs"][kStringAttrName].isObject());
+  EXPECT_EQ(node1["attrs"][kStringAttrName]["value"].asString(),
+            kStringAttrValue);
+  EXPECT_EQ(node1["attrs"][kStringAttrName]["type"].asString(), "string");
+  EXPECT_EQ(node1["attrs"][kStringAttrName]["units"].asString(), "");
+
+  Json::Value node2 = event["args"]["dumps"]["allocators"][kPath2];
+  EXPECT_TRUE(node2.isObject());
+  EXPECT_EQ(
+      node2["guid"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(node2_id.value)));
+  EXPECT_TRUE(node2["attrs"].empty());
+
+  Json::Value graph = event["args"]["dumps"]["allocators_graph"];
+  EXPECT_TRUE(graph.isArray());
+  EXPECT_EQ(graph.size(), 1u);
+  EXPECT_EQ(
+      graph[0]["source"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(node1_id.value)));
+  EXPECT_EQ(
+      graph[0]["target"].asString(),
+      base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(node2_id.value)));
+  EXPECT_EQ(graph[0]["importance"].asUInt(), kImportance);
+  EXPECT_EQ(graph[0]["type"].asString(), "ownership");
+}
+
 }  // namespace
 }  // namespace json
 }  // namespace trace_processor
diff --git a/src/trace_processor/forwarding_trace_parser.cc b/src/trace_processor/forwarding_trace_parser.cc
index 27cf542..6583ead 100644
--- a/src/trace_processor/forwarding_trace_parser.cc
+++ b/src/trace_processor/forwarding_trace_parser.cc
@@ -21,7 +21,7 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/ninja/ninja_log_parser.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
-#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
 #include "src/trace_processor/trace_sorter.h"
 
 namespace perfetto {
@@ -81,7 +81,7 @@
         PERFETTO_DLOG("Proto trace detected");
         // This will be reduced once we read the trace config and we see flush
         // period being set.
-        reader_.reset(new ProtoTraceTokenizer(context_));
+        reader_.reset(new ProtoTraceReader(context_));
         context_->sorter.reset(new TraceSorter(
             std::unique_ptr<TraceParser>(new ProtoTraceParser(context_)),
             kMaxWindowSize));
@@ -130,7 +130,9 @@
           return util::ErrStatus(kNoZlibErr);
         }
       case kUnknownTraceType:
-        return util::ErrStatus("Unknown trace type provided");
+        // If renaming this error message don't remove the "(ERR:fmt)" part.
+        // The UI's error_dialog.ts uses it to make the dialog more graceful.
+        return util::ErrStatus("Unknown trace type provided (ERR:fmt)");
     }
   }
 
@@ -159,7 +161,7 @@
     return kJsonTraceType;
 
   // Systrace with header but no leading HTML.
-  if (base::StartsWith(start, "# tracer"))
+  if (start.find("# tracer") != std::string::npos)
     return kSystraceTraceType;
 
   // Systrace with leading HTML.
@@ -183,7 +185,10 @@
   if (base::StartsWith(start, "\x1f\x8b"))
     return kGzipTraceType;
 
-  return kProtoTraceType;
+  if (base::StartsWith(start, "\x0a"))
+    return kProtoTraceType;
+
+  return kUnknownTraceType;
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/forwarding_trace_parser_unittest.cc b/src/trace_processor/forwarding_trace_parser_unittest.cc
index 78eb404..048ab14 100644
--- a/src/trace_processor/forwarding_trace_parser_unittest.cc
+++ b/src/trace_processor/forwarding_trace_parser_unittest.cc
@@ -58,6 +58,13 @@
   EXPECT_EQ(kFuchsiaTraceType, GuessTraceType(prefix, sizeof(prefix)));
 }
 
+TEST(TraceProcessorImplTest, GuessTraceType_Bmp) {
+  const uint8_t prefix[] = {0x42, 0x4d, 0x1e, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
+                            0x0c, 0x00, 0x00, 0x00, 0x01, 0x00};
+  EXPECT_EQ(kUnknownTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/BUILD.gn b/src/trace_processor/importers/BUILD.gn
index 082f7bb..f01421f 100644
--- a/src/trace_processor/importers/BUILD.gn
+++ b/src/trace_processor/importers/BUILD.gn
@@ -22,6 +22,8 @@
     "common/clock_tracker.h",
     "common/event_tracker.cc",
     "common/event_tracker.h",
+    "common/flow_tracker.cc",
+    "common/flow_tracker.h",
     "common/global_args_tracker.cc",
     "common/global_args_tracker.h",
     "common/process_tracker.cc",
@@ -33,6 +35,10 @@
     "common/track_tracker.cc",
     "common/track_tracker.h",
   ]
+  public_deps = [
+    ":gen_cc_config_descriptor",
+    "../util:protozero_to_text",
+  ]
   deps = [
     "../../../gn:default_deps",
     "../../../protos/perfetto/common:zero",
@@ -48,6 +54,7 @@
   sources = [
     "common/clock_tracker_unittest.cc",
     "common/event_tracker_unittest.cc",
+    "common/flow_tracker_unittest.cc",
     "common/process_tracker_unittest.cc",
     "common/slice_tracker_unittest.cc",
   ]
@@ -63,3 +70,28 @@
     "../types",
   ]
 }
+
+config("gen_config") {
+  include_dirs = [ "${root_gen_dir}/${perfetto_root_path}" ]
+}
+
+action("gen_cc_config_descriptor") {
+  descriptor_target = "../../../protos/perfetto/config:descriptor"
+  generated_header = "${target_gen_dir}/config.descriptor.h"
+
+  descriptor_file_path =
+      get_label_info(descriptor_target, "target_gen_dir") + "/config.descriptor"
+
+  script = "../../../tools/gen_cc_proto_descriptor.py"
+  deps = [ descriptor_target ]
+  args = [
+    "--gen_dir",
+    rebase_path(root_gen_dir, root_build_dir),
+    "--cpp_out",
+    rebase_path(generated_header, root_build_dir),
+    rebase_path(descriptor_file_path, root_build_dir),
+  ]
+  inputs = [ descriptor_file_path ]
+  outputs = [ generated_header ]
+  public_configs = [ ":gen_config" ]
+}
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index fb803ae..4b17787 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -108,6 +108,15 @@
     return AddArgsTo(context_->storage->mutable_slice_table(), id);
   }
 
+  BoundInserter AddArgsTo(tables::FlowTable::Id id) {
+    return AddArgsTo(context_->storage->mutable_flow_table(), id);
+  }
+
+  BoundInserter AddArgsTo(tables::MemorySnapshotNodeTable::Id id) {
+    return AddArgsTo(context_->storage->mutable_memory_snapshot_node_table(),
+                     id);
+  }
+
   BoundInserter AddArgsTo(MetadataId id) {
     auto* table = context_->storage->mutable_metadata_table();
     uint32_t row = *table->id().IndexOf(id);
diff --git a/src/trace_processor/importers/common/clock_tracker.cc b/src/trace_processor/importers/common/clock_tracker.cc
index 4f52029..f59fbfc 100644
--- a/src/trace_processor/importers/common/clock_tracker.cc
+++ b/src/trace_processor/importers/common/clock_tracker.cc
@@ -19,6 +19,7 @@
 #include <inttypes.h>
 
 #include <algorithm>
+#include <atomic>
 #include <queue>
 
 #include "perfetto/base/logging.h"
@@ -209,9 +210,13 @@
 
   ClockPath path = FindPath(src_clock_id, target_clock_id);
   if (!path.valid()) {
-    PERFETTO_DLOG("No path from clock %" PRIu64 " to %" PRIu64
-                  " at timestamp %" PRId64,
-                  src_clock_id, target_clock_id, src_timestamp);
+    // Too many logs maybe emitted when path is invalid.
+    static std::atomic<uint32_t> dlog_count(0);
+    if (dlog_count++ < 10) {
+      PERFETTO_DLOG("No path from clock %" PRIu64 " to %" PRIu64
+                    " at timestamp %" PRId64,
+                    src_clock_id, target_clock_id, src_timestamp);
+    }
     context_->storage->IncrementStats(stats::clock_sync_failure);
     return base::nullopt;
   }
@@ -251,8 +256,8 @@
     // And use that to retrieve the corresponding time in the next clock domain.
     // The snapshot id must exist in the target clock domain. If it doesn't
     // either the hash logic or the pathfinding logic are bugged.
-    // This can also happen if the sanity checks in AddSnapshot fail and we
-    // skip part of the snapshot.
+    // This can also happen if the checks in AddSnapshot fail and we skip part
+    // of the snapshot.
     const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);
 
     // Using std::lower_bound because snapshot_ids is sorted, so we can do
diff --git a/src/trace_processor/importers/common/flow_tracker.cc b/src/trace_processor/importers/common/flow_tracker.cc
new file mode 100644
index 0000000..90a2572
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+
+#include <stdint.h>
+
+#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+FlowTracker::FlowTracker(TraceProcessorContext* context) : context_(context) {
+  name_key_id_ = context_->storage->InternString("name");
+  cat_key_id_ = context_->storage->InternString("cat");
+}
+
+FlowTracker::~FlowTracker() = default;
+
+/* TODO: if we report a flow event earlier that a corresponding slice then
+  flow event would not be added, and it will increase "flow_no_enclosing_slice"
+  In catapult, it was possible to report a flow after an enclosing slice if
+  timestamps were equal. But because of our seqential processing of a trace
+  it is a bit tricky to make it here.
+  We suspect that this case is too rare or impossible */
+void FlowTracker::Begin(TrackId track_id, FlowId flow_id) {
+  base::Optional<SliceId> open_slice_id =
+      context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+  if (!open_slice_id) {
+    context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+    return;
+  }
+  if (flow_to_slice_map_.count(flow_id) != 0) {
+    context_->storage->IncrementStats(stats::flow_duplicate_id);
+    return;
+  }
+  flow_to_slice_map_[flow_id] = open_slice_id.value();
+}
+
+void FlowTracker::Step(TrackId track_id, FlowId flow_id) {
+  base::Optional<SliceId> open_slice_id =
+      context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+  if (!open_slice_id) {
+    context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+    return;
+  }
+  if (flow_to_slice_map_.count(flow_id) == 0) {
+    context_->storage->IncrementStats(stats::flow_step_without_start);
+    return;
+  }
+  SliceId slice_out_id = flow_to_slice_map_[flow_id];
+  InsertFlow(flow_id, slice_out_id, open_slice_id.value());
+  flow_to_slice_map_[flow_id] = open_slice_id.value();
+}
+
+void FlowTracker::End(TrackId track_id,
+                      FlowId flow_id,
+                      bool bind_enclosing_slice,
+                      bool close_flow) {
+  if (!bind_enclosing_slice) {
+    pending_flow_ids_map_[track_id].push_back(flow_id);
+    return;
+  }
+  base::Optional<SliceId> open_slice_id =
+      context_->slice_tracker->GetTopmostSliceOnTrack(track_id);
+  if (!open_slice_id) {
+    context_->storage->IncrementStats(stats::flow_no_enclosing_slice);
+    return;
+  }
+  if (flow_to_slice_map_.count(flow_id) == 0) {
+    context_->storage->IncrementStats(stats::flow_end_without_start);
+    return;
+  }
+  SliceId slice_out_id = flow_to_slice_map_[flow_id];
+  if (close_flow) {
+    flow_to_slice_map_.erase(flow_to_slice_map_.find(flow_id));
+  }
+  InsertFlow(flow_id, slice_out_id, open_slice_id.value());
+}
+
+bool FlowTracker::IsActive(FlowId flow_id) const {
+  return flow_to_slice_map_.find(flow_id) != flow_to_slice_map_.end();
+}
+
+FlowId FlowTracker::GetFlowIdForV1Event(uint64_t source_id,
+                                        StringId cat,
+                                        StringId name) {
+  V1FlowId v1_flow_id = {source_id, cat, name};
+  auto iter = v1_flow_id_to_flow_id_map_.find(v1_flow_id);
+  if (iter != v1_flow_id_to_flow_id_map_.end()) {
+    return iter->second;
+  }
+  FlowId new_id = v1_id_counter_++;
+  flow_id_to_v1_flow_id_map_[new_id] = v1_flow_id;
+  v1_flow_id_to_flow_id_map_[v1_flow_id] = new_id;
+  return new_id;
+}
+
+void FlowTracker::ClosePendingEventsOnTrack(TrackId track_id,
+                                            SliceId slice_id) {
+  auto iter = pending_flow_ids_map_.find(track_id);
+  if (iter == pending_flow_ids_map_.end())
+    return;
+
+  for (FlowId flow_id : iter->second) {
+    SliceId slice_out_id = flow_to_slice_map_[flow_id];
+    InsertFlow(flow_id, slice_out_id, slice_id);
+  }
+
+  pending_flow_ids_map_.erase(iter);
+}
+
+void FlowTracker::InsertFlow(FlowId flow_id,
+                             SliceId slice_out_id,
+                             SliceId slice_in_id) {
+  tables::FlowTable::Row row(slice_out_id, slice_in_id, kInvalidArgSetId);
+  auto id = context_->storage->mutable_flow_table()->Insert(row).id;
+
+  auto it = flow_id_to_v1_flow_id_map_.find(flow_id);
+  if (it != flow_id_to_v1_flow_id_map_.end()) {
+    // TODO(b/168007725): Add any args from v1 flow events and also export them.
+    auto args_tracker = ArgsTracker(context_);
+    auto inserter = context_->args_tracker->AddArgsTo(id);
+    inserter.AddArg(name_key_id_, Variadic::String(it->second.name));
+    inserter.AddArg(cat_key_id_, Variadic::String(it->second.cat));
+    context_->args_tracker->Flush();
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/flow_tracker.h b/src/trace_processor/importers/common/flow_tracker.h
new file mode 100644
index 0000000..ffe8c59
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker.h
@@ -0,0 +1,107 @@
+/*
+ * 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_IMPORTERS_COMMON_FLOW_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_FLOW_TRACKER_H_
+
+#include <stdint.h>
+
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using FlowId = uint64_t;
+
+class FlowTracker {
+ public:
+  explicit FlowTracker(TraceProcessorContext*);
+  virtual ~FlowTracker();
+
+  virtual void Begin(TrackId track_id, FlowId flow_id);
+
+  virtual void Step(TrackId track_id, FlowId flow_id);
+
+  // When |bind_enclosing_slice| is true we will connect the flow to the
+  // currently open slice on the track, when false we will connect the flow to
+  // the next slice to be opened on the track.
+  // When |close_flow| is true it will mark this as the singular end of the
+  // flow, however if there are multiple end points this should be set to
+  // false. Both parameters are only needed for v1 flow events support
+  virtual void End(TrackId track_id,
+                   FlowId flow_id,
+                   bool bind_enclosing_slice,
+                   bool close_flow);
+
+  bool IsActive(FlowId flow_id) const;
+
+  FlowId GetFlowIdForV1Event(uint64_t source_id, StringId cat, StringId name);
+
+  void ClosePendingEventsOnTrack(TrackId track_id, SliceId slice_id);
+
+ private:
+  struct V1FlowId {
+    uint64_t source_id;
+    StringId cat;
+    StringId name;
+
+    bool operator==(const V1FlowId& o) const {
+      return o.source_id == source_id && o.cat == cat && o.name == name;
+    }
+  };
+
+  struct V1FlowIdHasher {
+    size_t operator()(const V1FlowId& c) const {
+      base::Hash hasher;
+      hasher.Update(c.source_id);
+      hasher.Update(c.cat.raw_id());
+      hasher.Update(c.name.raw_id());
+      return std::hash<uint64_t>{}(hasher.digest());
+    }
+  };
+
+  using FlowToSourceSliceMap = std::unordered_map<FlowId, SliceId>;
+  using PendingFlowsMap = std::unordered_map<TrackId, std::vector<FlowId>>;
+  using V1FlowIdToFlowIdMap =
+      std::unordered_map<V1FlowId, FlowId, V1FlowIdHasher>;
+  using FlowIdToV1FlowId = std::unordered_map<FlowId, V1FlowId>;
+
+  void InsertFlow(FlowId flow_id,
+                  SliceId outgoing_slice_id,
+                  SliceId incoming_slice_id);
+
+  // List of flow end calls waiting for the next slice
+  PendingFlowsMap pending_flow_ids_map_;
+
+  // Flows generated by Begin() or Step()
+  FlowToSourceSliceMap flow_to_slice_map_;
+
+  V1FlowIdToFlowIdMap v1_flow_id_to_flow_id_map_;
+  FlowIdToV1FlowId flow_id_to_v1_flow_id_map_;
+  uint32_t v1_id_counter_ = 0;
+
+  TraceProcessorContext* const context_;
+
+  StringId name_key_id_;
+  StringId cat_key_id_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_FLOW_TRACKER_H_
diff --git a/src/trace_processor/importers/common/flow_tracker_unittest.cc b/src/trace_processor/importers/common/flow_tracker_unittest.cc
new file mode 100644
index 0000000..d8af423
--- /dev/null
+++ b/src/trace_processor/importers/common/flow_tracker_unittest.cc
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using ::testing::Eq;
+
+TEST(FlowTrackerTest, SingleFlowEventExplicitInSliceBinding) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow_id = 1;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow_id);
+  slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  slice_tracker->Begin(140, track_2, StringId::Raw(2), StringId::Raw(2));
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+  tracker.End(track_2, flow_id, /* bind_enclosing = */ true,
+              /* close_flow = */ false);
+  slice_tracker->End(160, track_2, StringId::Raw(2), StringId::Raw(2));
+
+  const auto& flows = context.storage->flow_table();
+  EXPECT_EQ(flows.row_count(), 1u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, SingleFlowEventWaitForNextSlice) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow_id = 1;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow_id);
+  slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  tracker.End(track_2, flow_id, /* bind_enclosing = */ false,
+              /* close_flow = */ false);
+
+  const auto& flows = context.storage->flow_table();
+
+  EXPECT_EQ(flows.row_count(), 0u);
+
+  slice_tracker->Begin(140, track_2, StringId::Raw(2), StringId::Raw(2));
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+  slice_tracker->End(160, track_2, StringId::Raw(2), StringId::Raw(2));
+
+  EXPECT_EQ(flows.row_count(), 1u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, SingleFlowEventWaitForNextSliceScoped) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow_id = 1;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow_id);
+  slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  tracker.End(track_2, flow_id, /* bind_enclosing = */ false,
+              /* close_flow = */ false);
+
+  const auto& flows = context.storage->flow_table();
+
+  EXPECT_EQ(flows.row_count(), 0u);
+
+  slice_tracker->Scoped(140, track_2, StringId::Raw(2), StringId::Raw(2), 100);
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+
+  EXPECT_EQ(flows.row_count(), 1u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
+  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+}
+
+TEST(FlowTrackerTest, TwoFlowEventsWaitForNextSlice) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow1_id = 1;
+  FlowId flow2_id = 2;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  // begin flow1 in enclosing slice1
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice1_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow1_id);
+  tracker.End(track_2, flow1_id, /* bind_enclosing = */ false,
+              /* close_flow = */ false);
+  slice_tracker->End(120, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  // begin flow2 in enclosing slice2
+  slice_tracker->Begin(130, track_1, StringId::Raw(2), StringId::Raw(2));
+  SliceId out_slice2_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow2_id);
+  tracker.End(track_2, flow2_id, /* bind_enclosing = */ false,
+              /* close_flow = */ false);
+  slice_tracker->End(140, track_1, StringId::Raw(2), StringId::Raw(2));
+
+  const auto& flows = context.storage->flow_table();
+
+  EXPECT_EQ(flows.row_count(), 0u);
+
+  // close all pending flows
+  slice_tracker->Begin(160, track_2, StringId::Raw(3), StringId::Raw(3));
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+  slice_tracker->End(170, track_2, StringId::Raw(3), StringId::Raw(3));
+
+  EXPECT_EQ(flows.row_count(), 2u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
+  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+  EXPECT_EQ(flows.slice_out()[1], out_slice2_id);
+  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+TEST(FlowTrackerTest, TwoFlowEventsSliceInSlice) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow1_id = 1;
+  FlowId flow2_id = 2;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  // start two nested slices
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice1_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  slice_tracker->Begin(120, track_1, StringId::Raw(2), StringId::Raw(2));
+  SliceId out_slice2_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+
+  tracker.Begin(track_1, flow1_id);
+
+  slice_tracker->End(140, track_1, StringId::Raw(2), StringId::Raw(2));
+
+  tracker.Begin(track_1, flow2_id);
+
+  slice_tracker->End(150, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  slice_tracker->Begin(160, track_2, StringId::Raw(3), StringId::Raw(3));
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+
+  tracker.End(track_2, flow1_id, /* bind_enclosing = */ true,
+              /* close_flow = */ false);
+  tracker.End(track_2, flow2_id, /* bind_enclosing = */ true,
+              /* close_flow = */ false);
+
+  slice_tracker->End(170, track_2, StringId::Raw(3), StringId::Raw(3));
+
+  const auto& flows = context.storage->flow_table();
+  EXPECT_EQ(flows.row_count(), 2u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice2_id);
+  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+  EXPECT_EQ(flows.slice_out()[1], out_slice1_id);
+  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+TEST(FlowTrackerTest, FlowEventsWithStep) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  context.slice_tracker.reset(new SliceTracker(&context));
+  auto& slice_tracker = context.slice_tracker;
+  FlowTracker tracker(&context);
+  slice_tracker->SetOnSliceBeginCallback(
+      [&tracker](TrackId track_id, SliceId slice_id) {
+        tracker.ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
+  FlowId flow_id = 1;
+  TrackId track_1(1);
+  TrackId track_2(2);
+
+  // flow begin inside slice1 on track1
+  slice_tracker->Begin(100, track_1, StringId::Raw(1), StringId::Raw(1));
+  SliceId out_slice1_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.Begin(track_1, flow_id);
+  slice_tracker->End(140, track_1, StringId::Raw(1), StringId::Raw(1));
+
+  // flow step inside slice2 on track2
+  slice_tracker->Begin(160, track_2, StringId::Raw(2), StringId::Raw(2));
+  SliceId inout_slice2_id =
+      slice_tracker->GetTopmostSliceOnTrack(track_2).value();
+  tracker.Step(track_2, flow_id);
+  slice_tracker->End(170, track_2, StringId::Raw(2), StringId::Raw(2));
+
+  // flow end inside slice3 on track3
+  slice_tracker->Begin(180, track_1, StringId::Raw(3), StringId::Raw(3));
+  SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_1).value();
+  tracker.End(track_1, flow_id, /* bind_enclosing = */ true,
+              /* close_flow = */ false);
+  slice_tracker->End(190, track_1, StringId::Raw(3), StringId::Raw(3));
+
+  const auto& flows = context.storage->flow_table();
+  EXPECT_EQ(flows.row_count(), 2u);
+  EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
+  EXPECT_EQ(flows.slice_in()[0], inout_slice2_id);
+  EXPECT_EQ(flows.slice_out()[1], inout_slice2_id);
+  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index 2c0486d..7c25ef7 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -391,7 +391,8 @@
       pending_parent_assocs_.pop_back();
     }
 
-    for (auto it = pending_assocs_.begin(); it != pending_assocs_.end();) {
+    auto end = pending_assocs_.end();
+    for (auto it = pending_assocs_.begin(); it != end;) {
       UniqueTid other_utid;
       if (it->first == utid) {
         other_utid = it->second;
@@ -409,15 +410,20 @@
                       tt->upid()[other_utid] == upid);
       AssociateThreadToProcess(other_utid, upid);
 
-      // Erase the pair. The |pending_assocs_| vector is not sorted and swapping
-      // a std::pair<uint32_t, uint32_t> is cheap.
-      std::swap(*it, pending_assocs_.back());
-      pending_assocs_.pop_back();
+      // Swap the current element to the end of the list and move the end
+      // iterator back. This works because |pending_assocs_| is not sorted. We
+      // do it this way rather than modifying |pending_assocs_| directly to
+      // prevent undefined behaviour caused by modifying a vector while
+      // iterating through it.
+      std::swap(*it, *(--end));
 
       // Recurse into the newly resolved thread. Some other threads might have
       // been bound to that.
       resolved_utids.emplace_back(other_utid);
     }
+
+    // Make sure to actually erase the utids which have been resolved.
+    pending_assocs_.erase(end, pending_assocs_.end());
   }  // while (!resolved_utids.empty())
 }
 
diff --git a/src/trace_processor/importers/common/slice_tracker.cc b/src/trace_processor/importers/common/slice_tracker.cc
index 29d64cb..0a5ef04 100644
--- a/src/trace_processor/importers/common/slice_tracker.cc
+++ b/src/trace_processor/importers/common/slice_tracker.cc
@@ -53,7 +53,8 @@
   });
 }
 
-void SliceTracker::BeginLegacyUnnestable(tables::SliceTable::Row row) {
+void SliceTracker::BeginLegacyUnnestable(tables::SliceTable::Row row,
+                                         SetArgsCallback args_callback) {
   // Ensure that the duration is pending for this row.
   // TODO(lalitm): change this to eventually use null instead of -1.
   row.dur = kPendingDuration;
@@ -68,7 +69,7 @@
   // Ensure that StartSlice knows that this track is unnestable.
   stacks_[row.track_id].is_legacy_unnestable = true;
 
-  StartSlice(row.ts, row.track_id, SetArgsCallback{}, [this, &row]() {
+  StartSlice(row.ts, row.track_id, args_callback, [this, &row]() {
     return context_->storage->mutable_slice_table()->Insert(row).id;
   });
 }
@@ -214,13 +215,13 @@
 
     // If this is an unnestable track, don't start a new slice if one already
     // exists.
-    if (stack->size() != 0) {
+    if (!stack->empty()) {
       return base::nullopt;
     }
   }
 
   auto* slices = context_->storage->mutable_slice_table();
-  MaybeCloseStack(timestamp, stack);
+  MaybeCloseStack(timestamp, stack, track_id);
 
   const uint8_t depth = static_cast<uint8_t>(stack->size());
   if (depth >= std::numeric_limits<uint8_t>::max()) {
@@ -235,8 +236,7 @@
 
   SliceId id = inserter();
   uint32_t slice_idx = *slices->id().IndexOf(id);
-
-  stack->emplace_back(SliceInfo{slice_idx, ArgsTracker(context_)});
+  StackPush(track_id, slice_idx);
 
   // Post fill all the relevant columns. All the other columns should have
   // been filled by the inserter.
@@ -272,7 +272,7 @@
 
   TrackInfo& track_info = it->second;
   SlicesStack& stack = track_info.slice_stack;
-  MaybeCloseStack(timestamp, &stack);
+  MaybeCloseStack(timestamp, &stack, track_id);
   if (stack.empty())
     return base::nullopt;
 
@@ -347,7 +347,25 @@
   stacks_.clear();
 }
 
-void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
+void SliceTracker::SetOnSliceBeginCallback(OnSliceBeginCallback callback) {
+  on_slice_begin_callback_ = callback;
+}
+
+base::Optional<SliceId> SliceTracker::GetTopmostSliceOnTrack(
+    TrackId track_id) const {
+  const auto iter = stacks_.find(track_id);
+  if (iter == stacks_.end())
+    return base::nullopt;
+  const auto& stack = iter->second.slice_stack;
+  if (stack.empty())
+    return base::nullopt;
+  uint32_t slice_idx = stack.back().row;
+  return context_->storage->slice_table().id()[slice_idx];
+}
+
+void SliceTracker::MaybeCloseStack(int64_t ts,
+                                   SlicesStack* stack,
+                                   TrackId track_id) {
   auto* slices = context_->storage->mutable_slice_table();
   bool incomplete_descendent = false;
   for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
@@ -387,18 +405,18 @@
         uint32_t child_idx = (*stack)[static_cast<size_t>(j)].row;
         PERFETTO_DCHECK(slices->dur()[child_idx] == kPendingDuration);
         slices->mutable_dur()->Set(child_idx, end_ts - slices->ts()[child_idx]);
-        stack->pop_back();
+        StackPop(track_id);
       }
 
       // Also pop the current row itself and reset the incomplete flag.
-      stack->pop_back();
+      StackPop(track_id);
       incomplete_descendent = false;
 
       continue;
     }
 
     if (end_ts <= ts) {
-      stack->pop_back();
+      StackPop(track_id);
     }
   }
 }
@@ -424,5 +442,19 @@
   return static_cast<int64_t>(hash.digest() & kSafeBitmask);
 }
 
+void SliceTracker::StackPop(TrackId track_id) {
+  stacks_[track_id].slice_stack.pop_back();
+}
+
+void SliceTracker::StackPush(TrackId track_id, uint32_t slice_idx) {
+  stacks_[track_id].slice_stack.push_back(
+      SliceInfo{slice_idx, ArgsTracker(context_)});
+
+  const auto& slices = context_->storage->slice_table();
+  if (on_slice_begin_callback_) {
+    on_slice_begin_callback_(track_id, slices.id()[slice_idx]);
+  }
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/slice_tracker.h b/src/trace_processor/importers/common/slice_tracker.h
index 6cf02e4..197204a 100644
--- a/src/trace_processor/importers/common/slice_tracker.h
+++ b/src/trace_processor/importers/common/slice_tracker.h
@@ -31,6 +31,7 @@
 class SliceTracker {
  public:
   using SetArgsCallback = std::function<void(ArgsTracker::BoundInserter*)>;
+  using OnSliceBeginCallback = std::function<void(TrackId, SliceId)>;
 
   explicit SliceTracker(TraceProcessorContext*);
   virtual ~SliceTracker();
@@ -49,7 +50,8 @@
   // as the latest time we saw a begin event. For legacy Android use only. See
   // the comment in SystraceParser::ParseSystracePoint for information on why
   // this method exists.
-  void BeginLegacyUnnestable(tables::SliceTable::Row row);
+  void BeginLegacyUnnestable(tables::SliceTable::Row row,
+                             SetArgsCallback args_callback);
 
   void BeginGpu(tables::GpuSliceTable::Row row,
                 SetArgsCallback args_callback = SetArgsCallback());
@@ -102,6 +104,10 @@
 
   void FlushPendingSlices();
 
+  void SetOnSliceBeginCallback(OnSliceBeginCallback callback);
+
+  base::Optional<SliceId> GetTopmostSliceOnTrack(TrackId track_id) const;
+
  private:
   struct SliceInfo {
     uint32_t row;
@@ -130,14 +136,21 @@
       SetArgsCallback args_callback,
       std::function<base::Optional<uint32_t>(const SlicesStack&)> finder);
 
-  void MaybeCloseStack(int64_t end_ts, SlicesStack*);
+  void MaybeCloseStack(int64_t end_ts, SlicesStack*, TrackId track_id);
 
   base::Optional<uint32_t> MatchingIncompleteSliceIndex(
       const SlicesStack& stack,
       StringId name,
       StringId category);
+
   int64_t GetStackHash(const SlicesStack&);
 
+  void StackPop(TrackId track_id);
+  void StackPush(TrackId track_id, uint32_t slice_idx);
+  void FlowTrackerUpdate(TrackId track_id);
+
+  OnSliceBeginCallback on_slice_begin_callback_;
+
   // Timestamp of the previous event. Used to discard events arriving out
   // of order.
   int64_t prev_timestamp_ = 0;
diff --git a/src/trace_processor/importers/common/slice_tracker_unittest.cc b/src/trace_processor/importers/common/slice_tracker_unittest.cc
index 35b1f5b..929e186 100644
--- a/src/trace_processor/importers/common/slice_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/slice_tracker_unittest.cc
@@ -305,6 +305,72 @@
   EXPECT_EQ(context.storage->slice_table().depth()[3], 0u);
 }
 
+TEST(SliceTrackerTest, GetTopmostSliceOnTrack) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  SliceTracker tracker(&context);
+
+  TrackId track{1u};
+  TrackId track2{2u};
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track), base::nullopt);
+
+  tracker.Begin(100, track, StringId::Raw(11), StringId::Raw(11));
+  SliceId slice1 = context.storage->slice_table().id()[0];
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice1);
+
+  tracker.Begin(120, track, StringId::Raw(22), StringId::Raw(22));
+  SliceId slice2 = context.storage->slice_table().id()[1];
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice2);
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track2), base::nullopt);
+
+  tracker.End(140, track, StringId::Raw(22), StringId::Raw(22));
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice1);
+
+  tracker.End(330, track, StringId::Raw(11), StringId::Raw(11));
+
+  EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track), base::nullopt);
+}
+
+TEST(SliceTrackerTest, OnSliceBeginCallback) {
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  SliceTracker tracker(&context);
+
+  TrackId track1{1u};
+  TrackId track2{2u};
+
+  std::vector<TrackId> track_records;
+  std::vector<SliceId> slice_records;
+  tracker.SetOnSliceBeginCallback([&](TrackId track_id, SliceId slice_id) {
+    track_records.emplace_back(track_id);
+    slice_records.emplace_back(slice_id);
+  });
+
+  EXPECT_TRUE(track_records.empty());
+  EXPECT_TRUE(slice_records.empty());
+
+  tracker.Begin(100, track1, StringId::Raw(11), StringId::Raw(11));
+  SliceId slice1 = context.storage->slice_table().id()[0];
+  EXPECT_THAT(track_records, ElementsAre(TrackId{1u}));
+  EXPECT_THAT(slice_records, ElementsAre(slice1));
+
+  tracker.Begin(120, track2, StringId::Raw(22), StringId::Raw(22));
+  SliceId slice2 = context.storage->slice_table().id()[1];
+  EXPECT_THAT(track_records, ElementsAre(TrackId{1u}, TrackId{2u}));
+  EXPECT_THAT(slice_records, ElementsAre(slice1, slice2));
+
+  tracker.Begin(330, track1, StringId::Raw(33), StringId::Raw(33));
+  SliceId slice3 = context.storage->slice_table().id()[2];
+  EXPECT_THAT(track_records,
+              ElementsAre(TrackId{1u}, TrackId{2u}, TrackId{1u}));
+  EXPECT_THAT(slice_records, ElementsAre(slice1, slice2, slice3));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index e69afa3..6a4eb03 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -22,23 +22,16 @@
 namespace perfetto {
 namespace trace_processor {
 
-// static
-constexpr uint64_t TrackTracker::kDefaultDescriptorTrackUuid;
-
 TrackTracker::TrackTracker(TraceProcessorContext* context)
     : source_key_(context->storage->InternString("source")),
       source_id_key_(context->storage->InternString("source_id")),
       source_id_is_process_scoped_key_(
           context->storage->InternString("source_id_is_process_scoped")),
       source_scope_key_(context->storage->InternString("source_scope")),
-      parent_track_id_key_(context->storage->InternString("parent_track_id")),
       category_key_(context->storage->InternString("category")),
       fuchsia_source_(context->storage->InternString("fuchsia")),
       chrome_source_(context->storage->InternString("chrome")),
       android_source_(context->storage->InternString("android")),
-      descriptor_source_(context->storage->InternString("descriptor")),
-      default_descriptor_track_name_(
-          context->storage->InternString("Default Track")),
       context_(context) {}
 
 TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
@@ -66,20 +59,10 @@
 }
 
 TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
+                                              uint32_t upid,
                                               int64_t correlation_id) {
-  auto it = fuchsia_async_tracks_.find(correlation_id);
-  if (it != fuchsia_async_tracks_.end())
-    return it->second;
-
-  tables::TrackTable::Row row(name);
-  auto id = context_->storage->mutable_track_table()->Insert(row).id;
-  fuchsia_async_tracks_[correlation_id] = id;
-
-  context_->args_tracker->AddArgsTo(id)
-      .AddArg(source_key_, Variadic::String(fuchsia_source_))
-      .AddArg(source_id_key_, Variadic::Integer(correlation_id));
-
-  return id;
+  return InternLegacyChromeAsyncTrack(name, upid, correlation_id, false,
+                                      StringId());
 }
 
 TrackId TrackTracker::InternCpuTrack(StringId name, uint32_t cpu) {
@@ -120,8 +103,17 @@
   tuple.source_scope = source_scope;
 
   auto it = chrome_tracks_.find(tuple);
-  if (it != chrome_tracks_.end())
+  if (it != chrome_tracks_.end()) {
+    if (name != kNullStringId) {
+      // The track may have been created for an end event without name. In that
+      // case, update it with this event's name.
+      auto* tracks = context_->storage->mutable_track_table();
+      uint32_t track_row = *tracks->id().IndexOf(it->second);
+      if (tracks->name()[track_row] == kNullStringId)
+        tracks->mutable_name()->Set(track_row, name);
+    }
     return it->second;
+  }
 
   // Legacy async tracks are always drawn in the context of a process, even if
   // the ID's scope is global.
@@ -141,24 +133,12 @@
   return id;
 }
 
-TrackId TrackTracker::InternAndroidAsyncTrack(StringId name,
-                                              UniquePid upid,
-                                              int64_t cookie) {
-  AndroidAsyncTrackTuple tuple{upid, cookie, name};
-
-  auto it = android_async_tracks_.find(tuple);
-  if (it != android_async_tracks_.end())
-    return it->second;
-
+TrackId TrackTracker::CreateAndroidAsyncTrack(StringId name, UniquePid upid) {
   tables::ProcessTrackTable::Row row(name);
   row.upid = upid;
   auto id = context_->storage->mutable_process_track_table()->Insert(row).id;
-  android_async_tracks_[tuple] = id;
-
-  context_->args_tracker->AddArgsTo(id)
-      .AddArg(source_key_, Variadic::String(android_source_))
-      .AddArg(source_id_key_, Variadic::Integer(cookie));
-
+  context_->args_tracker->AddArgsTo(id).AddArg(
+      source_key_, Variadic::String(android_source_));
   return id;
 }
 
@@ -189,359 +169,6 @@
   return *chrome_global_instant_track_id_;
 }
 
-void TrackTracker::ReserveDescriptorProcessTrack(uint64_t uuid,
-                                                 StringId name,
-                                                 uint32_t pid,
-                                                 int64_t timestamp) {
-  DescriptorTrackReservation reservation;
-  reservation.min_timestamp = timestamp;
-  reservation.pid = pid;
-  reservation.name = name;
-
-  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
-  bool inserted;
-  std::tie(it, inserted) =
-      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
-  if (inserted)
-    return;
-
-  if (!it->second.IsForSameTrack(reservation)) {
-    // Process tracks should not be reassigned to a different pid later (neither
-    // should the type of the track change).
-    PERFETTO_DLOG("New track reservation for process track with uuid %" PRIu64
-                  " doesn't match earlier one",
-                  uuid);
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
-}
-
-void TrackTracker::ReserveDescriptorThreadTrack(uint64_t uuid,
-                                                uint64_t parent_uuid,
-                                                StringId name,
-                                                uint32_t pid,
-                                                uint32_t tid,
-                                                int64_t timestamp) {
-  DescriptorTrackReservation reservation;
-  reservation.min_timestamp = timestamp;
-  reservation.parent_uuid = parent_uuid;
-  reservation.pid = pid;
-  reservation.tid = tid;
-  reservation.name = name;
-
-  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
-  bool inserted;
-  std::tie(it, inserted) =
-      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
-  if (inserted)
-    return;
-
-  if (!it->second.IsForSameTrack(reservation)) {
-    // Thread tracks should not be reassigned to a different pid/tid later
-    // (neither should the type of the track change).
-    PERFETTO_DLOG("New track reservation for thread track with uuid %" PRIu64
-                  " doesn't match earlier one",
-                  uuid);
-    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-    return;
-  }
-
-  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
-}
-
-void TrackTracker::ReserveDescriptorCounterTrack(uint64_t uuid,
-                                                 uint64_t parent_uuid,
-                                                 StringId name,
-                                                 StringId category,
-                                                 int64_t unit_multiplier,
-                                                 bool is_incremental,
-                                                 uint32_t packet_sequence_id) {
-  DescriptorTrackReservation reservation;
-  reservation.parent_uuid = parent_uuid;
-  reservation.is_counter = true;
-  reservation.name = name;
-  reservation.category = category;
-  reservation.unit_multiplier = unit_multiplier;
-  reservation.is_incremental = is_incremental;
-  // Incrementally encoded counters are only valid on a single sequence.
-  if (is_incremental)
-    reservation.packet_sequence_id = packet_sequence_id;
-
-  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
-  bool inserted;
-  std::tie(it, inserted) =
-      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
-  if (inserted || it->second.IsForSameTrack(reservation))
-    return;
-
-  // Counter tracks should not be reassigned to a different parent track later
-  // (neither should the type of the track change).
-  PERFETTO_DLOG("New track reservation for counter track with uuid %" PRIu64
-                " doesn't match earlier one",
-                uuid);
-  context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-}
-
-void TrackTracker::ReserveDescriptorChildTrack(uint64_t uuid,
-                                               uint64_t parent_uuid,
-                                               StringId name) {
-  DescriptorTrackReservation reservation;
-  reservation.parent_uuid = parent_uuid;
-  reservation.name = name;
-
-  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
-  bool inserted;
-  std::tie(it, inserted) =
-      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
-
-  if (inserted || it->second.IsForSameTrack(reservation))
-    return;
-
-  // Child tracks should not be reassigned to a different parent track later
-  // (neither should the type of the track change).
-  PERFETTO_DLOG("New track reservation for child track with uuid %" PRIu64
-                " doesn't match earlier one",
-                uuid);
-  context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
-}
-
-base::Optional<TrackId> TrackTracker::GetDescriptorTrack(uint64_t uuid) {
-  return GetDescriptorTrackImpl(uuid);
-}
-
-base::Optional<TrackId> TrackTracker::GetDescriptorTrackImpl(
-    uint64_t uuid,
-    std::vector<uint64_t>* descendent_uuids) {
-  auto it = resolved_descriptor_tracks_.find(uuid);
-  if (it == resolved_descriptor_tracks_.end()) {
-    auto reservation_it = reserved_descriptor_tracks_.find(uuid);
-    if (reservation_it == reserved_descriptor_tracks_.end())
-      return base::nullopt;
-    TrackId track_id =
-        ResolveDescriptorTrack(uuid, reservation_it->second, descendent_uuids);
-    resolved_descriptor_tracks_[uuid] = track_id;
-    return track_id;
-  }
-  return it->second;
-}
-
-TrackId TrackTracker::ResolveDescriptorTrack(
-    uint64_t uuid,
-    const DescriptorTrackReservation& reservation,
-    std::vector<uint64_t>* descendent_uuids) {
-  auto set_track_name_and_return = [this, &reservation](TrackId track_id) {
-    // Initialize the track name here, so that, if a name was given in the
-    // reservation, it is set immediately after resolution takes place.
-    if (reservation.name != kNullStringId) {
-      auto* tracks = context_->storage->mutable_track_table();
-      tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id),
-                                  reservation.name);
-    }
-    return track_id;
-  };
-
-  static constexpr size_t kMaxAncestors = 10;
-
-  // Try to resolve any parent tracks recursively, too.
-  base::Optional<TrackId> parent_track_id;
-  if (reservation.parent_uuid) {
-    // Input data may contain loops or extremely long ancestor track chains. To
-    // avoid stack overflow in these situations, we keep track of the ancestors
-    // seen in the recursion.
-    std::unique_ptr<std::vector<uint64_t>> owned_descendent_uuids;
-    if (!descendent_uuids) {
-      owned_descendent_uuids.reset(new std::vector<uint64_t>());
-      descendent_uuids = owned_descendent_uuids.get();
-    }
-    descendent_uuids->push_back(uuid);
-
-    if (descendent_uuids->size() > kMaxAncestors) {
-      PERFETTO_ELOG(
-          "Too many ancestors in parent_track_uuid hierarchy at track %" PRIu64
-          " with parent %" PRIu64,
-          uuid, reservation.parent_uuid);
-    } else if (std::find(descendent_uuids->begin(), descendent_uuids->end(),
-                         reservation.parent_uuid) != descendent_uuids->end()) {
-      PERFETTO_ELOG(
-          "Loop detected in parent_track_uuid hierarchy at track %" PRIu64
-          " with parent %" PRIu64,
-          uuid, reservation.parent_uuid);
-    } else {
-      parent_track_id =
-          GetDescriptorTrackImpl(reservation.parent_uuid, descendent_uuids);
-      if (!parent_track_id) {
-        PERFETTO_ELOG("Unknown parent track %" PRIu64 " for track %" PRIu64,
-                      reservation.parent_uuid, uuid);
-      }
-    }
-
-    descendent_uuids->pop_back();
-    if (owned_descendent_uuids)
-      descendent_uuids = nullptr;
-  }
-
-  if (reservation.tid) {
-    UniqueTid utid = context_->process_tracker->UpdateThread(*reservation.tid,
-                                                             *reservation.pid);
-    auto it_and_inserted =
-        descriptor_uuids_by_utid_.insert(std::make_pair<>(utid, uuid));
-    if (!it_and_inserted.second) {
-      // We already saw a another track with a different uuid for this thread.
-      // Since there should only be one descriptor track for each thread, we
-      // assume that its tid was reused. So, start a new thread.
-      uint64_t old_uuid = it_and_inserted.first->second;
-      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
-
-      PERFETTO_DLOG("Detected tid reuse (pid: %" PRIu32 " tid: %" PRIu32
-                    ") from track descriptors (old uuid: %" PRIu64
-                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
-                    *reservation.pid, *reservation.tid, old_uuid, uuid,
-                    reservation.min_timestamp);
-
-      utid = context_->process_tracker->StartNewThread(base::nullopt,
-                                                       *reservation.tid);
-
-      // Associate the new thread with its process.
-      PERFETTO_CHECK(context_->process_tracker->UpdateThread(
-                         *reservation.tid, *reservation.pid) == utid);
-
-      descriptor_uuids_by_utid_[utid] = uuid;
-    }
-    return set_track_name_and_return(InternThreadTrack(utid));
-  }
-
-  if (reservation.pid) {
-    UniquePid upid =
-        context_->process_tracker->GetOrCreateProcess(*reservation.pid);
-    auto it_and_inserted =
-        descriptor_uuids_by_upid_.insert(std::make_pair<>(upid, uuid));
-    if (!it_and_inserted.second) {
-      // We already saw a another track with a different uuid for this process.
-      // Since there should only be one descriptor track for each process, we
-      // assume that its pid was reused. So, start a new process.
-      uint64_t old_uuid = it_and_inserted.first->second;
-      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
-
-      PERFETTO_DLOG("Detected pid reuse (pid: %" PRIu32
-                    ") from track descriptors (old uuid: %" PRIu64
-                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
-                    *reservation.pid, old_uuid, uuid,
-                    reservation.min_timestamp);
-
-      upid = context_->process_tracker->StartNewProcess(
-          base::nullopt, base::nullopt, *reservation.pid, kNullStringId);
-
-      descriptor_uuids_by_upid_[upid] = uuid;
-    }
-    return set_track_name_and_return(InternProcessTrack(upid));
-  }
-
-  base::Optional<TrackId> track_id;
-  if (parent_track_id) {
-    // If parent is a thread track, create another thread-associated track.
-    auto* thread_tracks = context_->storage->mutable_thread_track_table();
-    base::Optional<uint32_t> thread_track_index =
-        thread_tracks->id().IndexOf(*parent_track_id);
-    if (thread_track_index) {
-      if (reservation.is_counter) {
-        // Thread counter track.
-        auto* thread_counter_tracks =
-            context_->storage->mutable_thread_counter_track_table();
-        tables::ThreadCounterTrackTable::Row row;
-        row.utid = thread_tracks->utid()[*thread_track_index];
-        track_id = thread_counter_tracks->Insert(row).id;
-      } else {
-        // Thread slice track.
-        tables::ThreadTrackTable::Row row;
-        row.utid = thread_tracks->utid()[*thread_track_index];
-        track_id = thread_tracks->Insert(row).id;
-      }
-    } else {
-      // If parent is a process track, create another process-associated track.
-      auto* process_tracks = context_->storage->mutable_process_track_table();
-      base::Optional<uint32_t> process_track_index =
-          process_tracks->id().IndexOf(*parent_track_id);
-      if (process_track_index) {
-        if (reservation.is_counter) {
-          // Process counter track.
-          auto* thread_counter_tracks =
-              context_->storage->mutable_process_counter_track_table();
-          tables::ProcessCounterTrackTable::Row row;
-          row.upid = process_tracks->upid()[*process_track_index];
-          track_id = thread_counter_tracks->Insert(row).id;
-        } else {
-          // Process slice track.
-          tables::ProcessTrackTable::Row row;
-          row.upid = process_tracks->upid()[*process_track_index];
-          track_id = process_tracks->Insert(row).id;
-        }
-      }
-    }
-  }
-
-  // Otherwise create a global track.
-  if (!track_id) {
-    if (reservation.is_counter) {
-      // Global counter track.
-      tables::CounterTrackTable::Row row;
-      track_id =
-          context_->storage->mutable_counter_track_table()->Insert(row).id;
-    } else {
-      // Global slice track.
-      tables::TrackTable::Row row;
-      track_id = context_->storage->mutable_track_table()->Insert(row).id;
-    }
-    // The global track with no uuid is the default global track (e.g. for
-    // global instant events). Any other global tracks are considered children
-    // of the default track.
-    if (!parent_track_id && uuid) {
-      // Detect loops where the default track has a parent that itself is a
-      // global track (and thus should be parent of the default track).
-      if (descendent_uuids &&
-          std::find(descendent_uuids->begin(), descendent_uuids->end(),
-                    kDefaultDescriptorTrackUuid) != descendent_uuids->end()) {
-        PERFETTO_ELOG(
-            "Loop detected in parent_track_uuid hierarchy at track %" PRIu64
-            " with parent %" PRIu64,
-            uuid, kDefaultDescriptorTrackUuid);
-      } else {
-        parent_track_id = GetOrCreateDefaultDescriptorTrack();
-      }
-    }
-  }
-
-  auto args = context_->args_tracker->AddArgsTo(*track_id);
-  args.AddArg(source_key_, Variadic::String(descriptor_source_))
-      .AddArg(source_id_key_, Variadic::Integer(static_cast<int64_t>(uuid)));
-  if (parent_track_id) {
-    args.AddArg(parent_track_id_key_,
-                Variadic::Integer(parent_track_id->value));
-  }
-  if (reservation.category != kNullStringId) {
-    args.AddArg(category_key_, Variadic::String(reservation.category));
-  }
-  return set_track_name_and_return(*track_id);
-}
-
-TrackId TrackTracker::GetOrCreateDefaultDescriptorTrack() {
-  // If the default track was already reserved (e.g. because a producer emitted
-  // a descriptor for it) or created, resolve and return it.
-  base::Optional<TrackId> track_id =
-      GetDescriptorTrack(kDefaultDescriptorTrackUuid);
-  if (track_id)
-    return *track_id;
-
-  // Otherwise reserve a new track and resolve it.
-  ReserveDescriptorChildTrack(kDefaultDescriptorTrackUuid, /*parent_uuid=*/0,
-                              default_descriptor_track_name_);
-  return *GetDescriptorTrack(kDefaultDescriptorTrackUuid);
-}
-
 TrackId TrackTracker::GetOrCreateTriggerTrack() {
   if (trigger_track_id_) {
     return *trigger_track_id_;
@@ -552,13 +179,17 @@
   return *trigger_track_id_;
 }
 
-TrackId TrackTracker::InternGlobalCounterTrack(StringId name) {
+TrackId TrackTracker::InternGlobalCounterTrack(StringId name,
+                                               StringId unit,
+                                               StringId description) {
   auto it = global_counter_tracks_by_name_.find(name);
   if (it != global_counter_tracks_by_name_.end()) {
     return it->second;
   }
 
   tables::CounterTrackTable::Row row(name);
+  row.unit = unit;
+  row.description = description;
   TrackId track =
       context_->storage->mutable_counter_track_table()->Insert(row).id;
   global_counter_tracks_by_name_[name] = track;
@@ -595,7 +226,10 @@
   return track;
 }
 
-TrackId TrackTracker::InternProcessCounterTrack(StringId name, UniquePid upid) {
+TrackId TrackTracker::InternProcessCounterTrack(StringId name,
+                                                UniquePid upid,
+                                                StringId unit,
+                                                StringId description) {
   auto it = upid_counter_tracks_.find(std::make_pair(name, upid));
   if (it != upid_counter_tracks_.end()) {
     return it->second;
@@ -603,6 +237,8 @@
 
   tables::ProcessCounterTrackTable::Row row(name);
   row.upid = upid;
+  row.unit = unit;
+  row.description = description;
 
   TrackId track =
       context_->storage->mutable_process_counter_track_table()->Insert(row).id;
@@ -663,61 +299,5 @@
   return context_->storage->mutable_gpu_counter_track_table()->Insert(row).id;
 }
 
-base::Optional<int64_t> TrackTracker::ConvertToAbsoluteCounterValue(
-    uint64_t counter_track_uuid,
-    uint32_t packet_sequence_id,
-    int64_t value) {
-  auto reservation_it = reserved_descriptor_tracks_.find(counter_track_uuid);
-  if (reservation_it == reserved_descriptor_tracks_.end()) {
-    PERFETTO_DLOG("Unknown counter track with uuid %" PRIu64,
-                  counter_track_uuid);
-    return base::nullopt;
-  }
-
-  DescriptorTrackReservation& reservation = reservation_it->second;
-  if (!reservation.is_counter) {
-    PERFETTO_DLOG("Track with uuid %" PRIu64 " is not a counter track",
-                  counter_track_uuid);
-    return base::nullopt;
-  }
-
-  if (reservation.unit_multiplier > 0)
-    value *= reservation.unit_multiplier;
-
-  if (reservation.is_incremental) {
-    if (reservation.packet_sequence_id != packet_sequence_id) {
-      PERFETTO_DLOG(
-          "Incremental counter track with uuid %" PRIu64
-          " was updated from the wrong packet sequence (expected: %" PRIu32
-          " got:%" PRIu32 ")",
-          counter_track_uuid, reservation.packet_sequence_id,
-          packet_sequence_id);
-      return base::nullopt;
-    }
-
-    reservation.latest_value += value;
-    value = reservation.latest_value;
-  }
-
-  return value;
-}
-
-void TrackTracker::OnIncrementalStateCleared(uint32_t packet_sequence_id) {
-  // TODO(eseckler): Improve on the runtime complexity of this. At O(hundreds)
-  // of packet sequences, incremental state clearing at O(trace second), and
-  // total number of tracks in O(thousands), a linear scan through all tracks
-  // here might not be fast enough.
-  for (auto& entry : reserved_descriptor_tracks_) {
-    DescriptorTrackReservation& reservation = entry.second;
-    // Only consider incremental counter tracks for current sequence.
-    if (!reservation.is_counter || !reservation.is_incremental ||
-        reservation.packet_sequence_id != packet_sequence_id) {
-      continue;
-    }
-    // Reset their value to 0, see CounterDescriptor's |is_incremental|.
-    reservation.latest_value = 0;
-  }
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index 907726c..f1462f9 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -35,7 +35,9 @@
   TrackId InternProcessTrack(UniquePid upid);
 
   // Interns a Fuchsia async track into the storage.
-  TrackId InternFuchsiaAsyncTrack(StringId name, int64_t correlation_id);
+  TrackId InternFuchsiaAsyncTrack(StringId name,
+                                  uint32_t upid,
+                                  int64_t correlation_id);
 
   // Interns a global track keyed by CPU + name into the storage.
   TrackId InternCpuTrack(StringId name, uint32_t cpu);
@@ -50,10 +52,8 @@
                                        bool source_id_is_process_scoped,
                                        StringId source_scope);
 
-  // Interns a Android async track into the storage.
-  TrackId InternAndroidAsyncTrack(StringId name,
-                                  UniquePid upid,
-                                  int64_t cookie);
+  // Creates and inserts a Android async track into the storage.
+  TrackId CreateAndroidAsyncTrack(StringId name, UniquePid upid);
 
   // Interns a track for legacy Chrome process-scoped instant events into the
   // storage.
@@ -62,88 +62,14 @@
   // Lazily creates the track for legacy Chrome global instant events.
   TrackId GetOrCreateLegacyChromeGlobalInstantTrack();
 
-  // Associate a TrackDescriptor track identified by the given |uuid| with a
-  // process's |pid|. This is called during tokenization. If a reservation for
-  // the same |uuid| already exists, verifies that the present reservation
-  // matches the new one.
-  //
-  // The track will be resolved to the process track (see InternProcessTrack())
-  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
-  // time, |pid| will also be resolved to a |upid|.
-  void ReserveDescriptorProcessTrack(uint64_t uuid,
-                                     StringId name,
-                                     uint32_t pid,
-                                     int64_t timestamp);
-
-  // Associate a TrackDescriptor track identified by the given |uuid| with a
-  // thread's |pid| and |tid|. This is called during tokenization. If a
-  // reservation for the same |uuid| already exists, verifies that the present
-  // reservation matches the new one.
-  //
-  // The track will be resolved to the thread track (see InternThreadTrack())
-  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
-  // time, |pid| will also be resolved to a |upid|.
-  void ReserveDescriptorThreadTrack(uint64_t uuid,
-                                    uint64_t parent_uuid,
-                                    StringId name,
-                                    uint32_t pid,
-                                    uint32_t tid,
-                                    int64_t timestamp);
-
-  // Associate a TrackDescriptor track identified by the given |uuid| with a
-  // parent track (usually a process- or thread-associated track). This is
-  // called during tokenization. If a reservation for the same |uuid| already
-  // exists, will attempt to update it.
-  //
-  // The track will be created upon the first call to GetDescriptorTrack() with
-  // the same |uuid|. If |parent_uuid| is 0, the track will become a global
-  // track. Otherwise, it will become a new track of the same type as its parent
-  // track.
-  void ReserveDescriptorChildTrack(uint64_t uuid,
-                                   uint64_t parent_uuid,
-                                   StringId name);
-
-  // Associate a counter-type TrackDescriptor track identified by the given
-  // |uuid| with a parent track (usually a process or thread track). This is
-  // called during tokenization. If a reservation for the same |uuid| already
-  // exists, will attempt to update it. The provided |category| will be stored
-  // into the track's args.
-  //
-  // If |is_incremental| is true, the counter will only be valid on the packet
-  // sequence identified by |packet_sequence_id|. |unit_multiplier| is an
-  // optional multiplication factor applied to counter values. Values for the
-  // counter will be translated during tokenization via
-  // ConvertToAbsoluteCounterValue().
-  //
-  // The track will be created upon the first call to GetDescriptorTrack() with
-  // the same |uuid|. If |parent_uuid| is 0, the track will become a global
-  // track. Otherwise, it will become a new counter track for the same
-  // process/thread as its parent track.
-  void ReserveDescriptorCounterTrack(uint64_t uuid,
-                                     uint64_t parent_uuid,
-                                     StringId name,
-                                     StringId category,
-                                     int64_t unit_multiplier,
-                                     bool is_incremental,
-                                     uint32_t packet_sequence_id);
-
-  // Returns the ID of the track for the TrackDescriptor with the given |uuid|.
-  // This is called during parsing. The first call to GetDescriptorTrack() for
-  // each |uuid| resolves and inserts the track (and its parent tracks,
-  // following the parent_uuid chain recursively) based on reservations made for
-  // the |uuid|. Returns nullopt if no track for a descriptor with this |uuid|
-  // has been reserved.
-  base::Optional<TrackId> GetDescriptorTrack(uint64_t uuid);
-
-  // Returns the ID of the implicit trace-global default TrackDescriptor track.
-  TrackId GetOrCreateDefaultDescriptorTrack();
-
   // Returns the ID of the implicit trace-global default track for triggers
   // received by the service.
   TrackId GetOrCreateTriggerTrack();
 
   // Interns a global counter track into the storage.
-  TrackId InternGlobalCounterTrack(StringId name);
+  TrackId InternGlobalCounterTrack(StringId name,
+                                   StringId unit = kNullStringId,
+                                   StringId description = kNullStringId);
 
   // Interns a counter track associated with a cpu into the storage.
   TrackId InternCpuCounterTrack(StringId name, uint32_t cpu);
@@ -152,7 +78,10 @@
   TrackId InternThreadCounterTrack(StringId name, UniqueTid utid);
 
   // Interns a counter track associated with a process into the storage.
-  TrackId InternProcessCounterTrack(StringId name, UniquePid upid);
+  TrackId InternProcessCounterTrack(StringId name,
+                                    UniquePid upid,
+                                    StringId unit = kNullStringId,
+                                    StringId description = kNullStringId);
 
   // Interns a counter track associated with an irq into the storage.
   TrackId InternIrqCounterTrack(StringId name, int32_t irq);
@@ -169,21 +98,6 @@
                                 StringId description = StringId::Null(),
                                 StringId unit = StringId::Null());
 
-  // Converts the given counter value to an absolute value in the unit of the
-  // counter, applying incremental delta encoding or unit multipliers as
-  // necessary. If the counter uses incremental encoding, |packet_sequence_id|
-  // must match the one in its track reservation. Returns base::nullopt if the
-  // counter track is unknown or an invalid |packet_sequence_id| was passed.
-  base::Optional<int64_t> ConvertToAbsoluteCounterValue(
-      uint64_t counter_track_uuid,
-      uint32_t packet_sequence_id,
-      int64_t value);
-
-  // Called by ProtoTraceTokenizer whenever incremental state is cleared on a
-  // packet sequence. Resets counter values for any incremental counters of
-  // the sequence identified by |packet_sequence_id|.
-  void OnIncrementalStateCleared(uint32_t packet_sequence_id);
-
  private:
   struct GpuTrackTuple {
     StringId track_name;
@@ -206,52 +120,6 @@
              std::tie(r.source_id, r.upid, r.source_scope);
     }
   };
-  struct AndroidAsyncTrackTuple {
-    UniquePid upid;
-    int64_t cookie;
-    StringId name;
-
-    friend bool operator<(const AndroidAsyncTrackTuple& l,
-                          const AndroidAsyncTrackTuple& r) {
-      return std::tie(l.upid, l.cookie, l.name) <
-             std::tie(r.upid, r.cookie, r.name);
-    }
-  };
-  struct DescriptorTrackReservation {
-    uint64_t parent_uuid = 0;
-    base::Optional<uint32_t> pid;
-    base::Optional<uint32_t> tid;
-    int64_t min_timestamp = 0;  // only set if |pid| and/or |tid| is set.
-    StringId name = kNullStringId;
-
-    // For counter tracks.
-    bool is_counter = false;
-    StringId category = kNullStringId;
-    int64_t unit_multiplier = 1;
-    bool is_incremental = false;
-    uint32_t packet_sequence_id = 0;
-    int64_t latest_value = 0;
-
-    // Whether |other| is a valid descriptor for this track reservation. A track
-    // should always remain nested underneath its original parent.
-    bool IsForSameTrack(const DescriptorTrackReservation& other) {
-      // Note that |min_timestamp|, |latest_value|, and |name| are ignored for
-      // this comparison.
-      return std::tie(parent_uuid, pid, tid, is_counter, category,
-                      unit_multiplier, is_incremental, packet_sequence_id) ==
-             std::tie(other.parent_uuid, pid, tid, is_counter, category,
-                      unit_multiplier, is_incremental, packet_sequence_id);
-    }
-  };
-
-  base::Optional<TrackId> GetDescriptorTrackImpl(
-      uint64_t uuid,
-      std::vector<uint64_t>* descendent_uuids = nullptr);
-  TrackId ResolveDescriptorTrack(uint64_t uuid,
-                                 const DescriptorTrackReservation&,
-                                 std::vector<uint64_t>* descendent_uuids);
-
-  static constexpr uint64_t kDefaultDescriptorTrackUuid = 0u;
 
   std::map<UniqueTid, TrackId> thread_tracks_;
   std::map<UniquePid, TrackId> process_tracks_;
@@ -261,12 +129,7 @@
 
   std::map<GpuTrackTuple, TrackId> gpu_tracks_;
   std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
-  std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
   std::map<UniquePid, TrackId> chrome_process_instant_tracks_;
-  base::Optional<TrackId> chrome_global_instant_track_id_;
-  std::map<uint64_t /* uuid */, DescriptorTrackReservation>
-      reserved_descriptor_tracks_;
-  std::map<uint64_t /* uuid */, TrackId> resolved_descriptor_tracks_;
   std::map<UniquePid, TrackId> perf_stack_tracks_;
 
   std::map<StringId, TrackId> global_counter_tracks_by_name_;
@@ -277,26 +140,18 @@
   std::map<std::pair<StringId, int32_t>, TrackId> softirq_counter_tracks_;
   std::map<std::pair<StringId, uint32_t>, TrackId> gpu_counter_tracks_;
 
-  // Stores the descriptor uuid used for the primary process/thread track
-  // for the given upid / utid. Used for pid/tid reuse detection.
-  std::map<UniquePid, uint64_t /*uuid*/> descriptor_uuids_by_upid_;
-  std::map<UniqueTid, uint64_t /*uuid*/> descriptor_uuids_by_utid_;
-
+  base::Optional<TrackId> chrome_global_instant_track_id_;
   base::Optional<TrackId> trigger_track_id_;
 
   const StringId source_key_ = kNullStringId;
   const StringId source_id_key_ = kNullStringId;
   const StringId source_id_is_process_scoped_key_ = kNullStringId;
   const StringId source_scope_key_ = kNullStringId;
-  const StringId parent_track_id_key_ = kNullStringId;
   const StringId category_key_ = kNullStringId;
 
   const StringId fuchsia_source_ = kNullStringId;
   const StringId chrome_source_ = kNullStringId;
   const StringId android_source_ = kNullStringId;
-  const StringId descriptor_source_ = kNullStringId;
-
-  const StringId default_descriptor_track_name_ = kNullStringId;
 
   TraceProcessorContext* const context_;
 };
diff --git a/src/trace_processor/importers/default_modules.cc b/src/trace_processor/importers/default_modules.cc
index 2704478..88fa696 100644
--- a/src/trace_processor/importers/default_modules.cc
+++ b/src/trace_processor/importers/default_modules.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/importers/default_modules.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h"
 #include "src/trace_processor/importers/proto/profile_module.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
@@ -30,6 +31,7 @@
   context->ftrace_module =
       static_cast<FtraceModule*>(context->modules.back().get());
 
+  context->modules.emplace_back(new MemoryTrackerSnapshotModule(context));
   context->modules.emplace_back(new TrackEventModule(context));
   context->modules.emplace_back(new ProfileModule(context));
 }
diff --git a/src/trace_processor/importers/ftrace/binder_tracker.cc b/src/trace_processor/importers/ftrace/binder_tracker.cc
index b6ef00e..143e9a6 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker.cc
@@ -138,11 +138,8 @@
                                    transaction_slice_id_, args_inserter);
     transaction_await_rcv[transaction_id] = track_id;
   } else {
-    // TODO(taylori): Change these to instant events with 0 duration
-    // once instants are displayed properly.
     context_->slice_tracker->Scoped(ts, track_id, binder_category_id_,
-                                    transaction_async_id_, 10000,
-                                    args_inserter);
+                                    transaction_async_id_, 0, args_inserter);
     awaiting_async_rcv_[transaction_id] = args_inserter;
   }
 }
@@ -195,7 +192,7 @@
   if (awaiting_async_rcv_.count(transaction_id) > 0) {
     auto args = awaiting_async_rcv_[transaction_id];
     context_->slice_tracker->Scoped(ts, track_id, binder_category_id_,
-                                    async_rcv_id_, 10000, args);
+                                    async_rcv_id_, 0, args);
     awaiting_async_rcv_.erase(transaction_id);
     return;
   }
@@ -203,6 +200,11 @@
 
 void BinderTracker::Lock(int64_t ts, uint32_t pid) {
   attempt_lock_[pid] = ts;
+
+  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+  TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+  context_->slice_tracker->Begin(ts, track_id, binder_category_id_,
+                                 lock_waiting_id_);
 }
 
 void BinderTracker::Locked(int64_t ts, uint32_t pid) {
@@ -212,9 +214,10 @@
     return;
 
   TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-  context_->slice_tracker->Scoped(attempt_lock_[pid], track_id,
-                                  binder_category_id_, lock_waiting_id_,
-                                  ts - attempt_lock_[pid]);
+  context_->slice_tracker->End(ts, track_id, binder_category_id_,
+                               lock_waiting_id_);
+  context_->slice_tracker->Begin(ts, track_id, binder_category_id_,
+                                 lock_held_id_);
 
   lock_acquired_[pid] = ts;
   attempt_lock_.erase(pid);
@@ -227,9 +230,8 @@
     return;
 
   TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-  context_->slice_tracker->Scoped(lock_acquired_[pid], track_id,
-                                  binder_category_id_, lock_held_id_,
-                                  ts - lock_acquired_[pid]);
+  context_->slice_tracker->End(ts, track_id, binder_category_id_,
+                               lock_held_id_);
   lock_acquired_.erase(pid);
 }
 
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index e46321b..413f90e 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<MessageDescriptor, 343> descriptors{{
+std::array<MessageDescriptor, 350> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -3650,6 +3650,86 @@
             {"type", ProtoSchemaType::kString},
         },
     },
+    {
+        "cpuhp_exit",
+        4,
+        {
+            {},
+            {"cpu", ProtoSchemaType::kUint32},
+            {"idx", ProtoSchemaType::kInt32},
+            {"ret", ProtoSchemaType::kInt32},
+            {"state", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "cpuhp_multi_enter",
+        4,
+        {
+            {},
+            {"cpu", ProtoSchemaType::kUint32},
+            {"fun", ProtoSchemaType::kUint64},
+            {"idx", ProtoSchemaType::kInt32},
+            {"target", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "cpuhp_enter",
+        4,
+        {
+            {},
+            {"cpu", ProtoSchemaType::kUint32},
+            {"fun", ProtoSchemaType::kUint64},
+            {"idx", ProtoSchemaType::kInt32},
+            {"target", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "cpuhp_latency",
+        4,
+        {
+            {},
+            {"cpu", ProtoSchemaType::kUint32},
+            {"ret", ProtoSchemaType::kInt32},
+            {"state", ProtoSchemaType::kUint32},
+            {"time", ProtoSchemaType::kUint64},
+        },
+    },
+    {
+        "fastrpc_dma_stat",
+        3,
+        {
+            {},
+            {"cid", ProtoSchemaType::kInt32},
+            {"len", ProtoSchemaType::kInt64},
+            {"total_allocated", ProtoSchemaType::kUint64},
+        },
+    },
+    {
+        "dpu_tracing_mark_write",
+        6,
+        {
+            {},
+            {"pid", ProtoSchemaType::kInt32},
+            {"trace_name", ProtoSchemaType::kString},
+            {"trace_begin", ProtoSchemaType::kUint32},
+            {"name", ProtoSchemaType::kString},
+            {"type", ProtoSchemaType::kUint32},
+            {"value", ProtoSchemaType::kInt32},
+        },
+    },
+    {
+        "g2d_tracing_mark_write",
+        6,
+        {
+            {},
+            {"pid", ProtoSchemaType::kInt32},
+            {"trace_name", ProtoSchemaType::kString},
+            {"trace_begin", ProtoSchemaType::kUint32},
+            {"name", ProtoSchemaType::kString},
+            {"type", ProtoSchemaType::kUint32},
+            {"value", ProtoSchemaType::kInt32},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
index 63f926b..5d2a4e4 100644
--- a/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_module_impl.cc
@@ -38,12 +38,13 @@
     const protos::pbzero::TracePacket::Decoder& decoder,
     TraceBlobView* packet,
     int64_t /*packet_timestamp*/,
-    PacketSequenceState* /*state*/,
+    PacketSequenceState* seq_state,
     uint32_t field_id) {
   if (field_id == TracePacket::kFtraceEventsFieldNumber) {
     auto ftrace_field = decoder.ftrace_events();
     const size_t fld_off = packet->offset_of(ftrace_field.data);
-    tokenizer_.TokenizeFtraceBundle(packet->slice(fld_off, ftrace_field.size));
+    tokenizer_.TokenizeFtraceBundle(packet->slice(fld_off, ftrace_field.size),
+                                    seq_state);
     return ModuleResult::Handled();
   }
   return ModuleResult::Ignored();
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 91ac454..6fb1cec 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -27,10 +27,14 @@
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/softirq_action.h"
 
+#include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
 #include "protos/perfetto/trace/ftrace/binder.pbzero.h"
+#include "protos/perfetto/trace/ftrace/dpu.pbzero.h"
+#include "protos/perfetto/trace/ftrace/fastrpc.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h"
+#include "protos/perfetto/trace/ftrace/g2d.pbzero.h"
 #include "protos/perfetto/trace/ftrace/generic.pbzero.h"
 #include "protos/perfetto/trace/ftrace/gpu_mem.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ion.pbzero.h"
@@ -49,6 +53,7 @@
 #include "protos/perfetto/trace/ftrace/task.pbzero.h"
 #include "protos/perfetto/trace/ftrace/thermal.pbzero.h"
 #include "protos/perfetto/trace/ftrace/workqueue.pbzero.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -63,6 +68,29 @@
 const uint32_t kKthreaddPid = 2;
 const char kKthreaddName[] = "kthreadd";
 
+struct FtraceEventAndFieldId {
+  uint32_t event_id;
+  uint32_t field_id;
+};
+
+// Contains a list of all the proto fields in ftrace events which represent
+// kernel functions. This list is used to convert the iids in these fields to
+// proper kernel symbols.
+// TODO(lalitm): going through this array is O(n) on a hot-path (see
+// ParseTypedFtraceToRaw). Consider changing this if we end up adding a lot of
+// events here.
+constexpr auto kKernelFunctionFields = std::array<FtraceEventAndFieldId, 3>{
+    {FtraceEventAndFieldId{
+         protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber,
+         protos::pbzero::SchedBlockedReasonFtraceEvent::kCallerFieldNumber},
+     FtraceEventAndFieldId{
+         protos::pbzero::FtraceEvent::kWorkqueueExecuteStartFieldNumber,
+         protos::pbzero::WorkqueueExecuteStartFtraceEvent::
+             kFunctionFieldNumber},
+     FtraceEventAndFieldId{
+         protos::pbzero::FtraceEvent::kWorkqueueQueueWorkFieldNumber,
+         protos::pbzero::WorkqueueQueueWorkFtraceEvent::kFunctionFieldNumber}}};
+
 }  // namespace
 
 FtraceParser::FtraceParser(TraceProcessorContext* context)
@@ -89,10 +117,17 @@
       irq_id_(context_->storage->InternString("irq")),
       ret_arg_id_(context_->storage->InternString("ret")),
       vec_arg_id_(context->storage->InternString("vec")),
-      gpu_mem_total_process_id_(
-          context->storage->InternString("gpumemtotal.process")),
-      gpu_mem_total_global_id_(
-          context->storage->InternString("gpumemtotal.global")) {
+      gpu_mem_total_name_id_(context->storage->InternString("GPU Memory")),
+      gpu_mem_total_unit_id_(context->storage->InternString(
+          std::to_string(protos::pbzero::GpuCounterDescriptor::BYTE).c_str())),
+      gpu_mem_total_global_desc_id_(context->storage->InternString(
+          "Total GPU memory used by the entire system")),
+      gpu_mem_total_proc_desc_id_(context->storage->InternString(
+          "Total GPU memory used by this process")),
+      sched_blocked_reason_id_(
+          context->storage->InternString("sched_blocked_reason")),
+      io_wait_id_(context->storage->InternString("io_wait")),
+      function_id_(context->storage->InternString("function")) {
   // Build the lookup table for the strings inside ftrace events (e.g. the
   // name of ftrace event fields and the names of their args).
   for (size_t i = 0; i < GetDescriptorsSize(); i++) {
@@ -116,6 +151,13 @@
     ftrace_message_strings_.emplace_back(ftrace_strings);
   }
 
+  // Array initialization causes a spurious warning due to llvm bug.
+  // See https://bugs.llvm.org/show_bug.cgi?id=21629 
+  fast_rpc_counter_names_[0] = context->storage->InternString("mem.fastrpc[ASDP]");
+  fast_rpc_counter_names_[1] = context->storage->InternString("mem.fastrpc[MDSP]");
+  fast_rpc_counter_names_[2] = context->storage->InternString("mem.fastrpc[SDSP]");
+  fast_rpc_counter_names_[3] = context->storage->InternString("mem.fastrpc[CDSP]");
+
   mm_event_counter_names_ = {
       {MmEventCounterNames(
            context->storage->InternString("mem.mm.min_flt.count"),
@@ -264,7 +306,9 @@
   }
 
   PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kFtraceEvent);
-  const TraceBlobView& event = ttp.ftrace_event;
+  const TraceBlobView& event = ttp.ftrace_event.event;
+  PacketSequenceStateGeneration* seq_state =
+      ttp.ftrace_event.sequence_state.get();
   ProtoDecoder decoder(event.data(), event.length());
   uint64_t raw_pid = 0;
   if (auto pid_field = decoder.FindField(FtraceEvent::kPidFieldNumber)) {
@@ -285,7 +329,7 @@
       ParseGenericFtrace(ts, cpu, pid, data);
     } else if (fld.id() != FtraceEvent::kSchedSwitchFieldNumber) {
       // sched_switch parsing populates the raw table by itself
-      ParseTypedFtraceToRaw(fld.id(), ts, cpu, pid, data);
+      ParseTypedFtraceToRaw(fld.id(), ts, cpu, pid, data, seq_state);
     }
 
     switch (fld.id()) {
@@ -430,7 +474,7 @@
         break;
       }
       case FtraceEvent::kWorkqueueExecuteStartFieldNumber: {
-        ParseWorkqueueExecuteStart(ts, pid, data);
+        ParseWorkqueueExecuteStart(ts, pid, data, seq_state);
         break;
       }
       case FtraceEvent::kWorkqueueExecuteEndFieldNumber: {
@@ -465,6 +509,22 @@
         ParseCdevUpdate(ts, data);
         break;
       }
+      case FtraceEvent::kSchedBlockedReasonFieldNumber: {
+        ParseSchedBlockedReason(ts, data, seq_state);
+        break;
+      }
+      case FtraceEvent::kFastrpcDmaStatFieldNumber: {
+        ParseFastRpcDmaStat(ts, pid, data);
+        break;
+      }
+      case FtraceEvent::kG2dTracingMarkWriteFieldNumber: {
+        ParseG2dTracingMarkWrite(ts, pid, data);
+        break;
+      }
+      case FtraceEvent::kDpuTracingMarkWriteFieldNumber: {
+        ParseDpuTracingMarkWrite(ts, pid, data);
+        break;
+      }
       default:
         break;
     }
@@ -502,11 +562,13 @@
   }
 }
 
-void FtraceParser::ParseTypedFtraceToRaw(uint32_t ftrace_id,
-                                         int64_t ts,
-                                         uint32_t cpu,
-                                         uint32_t tid,
-                                         ConstBytes blob) {
+void FtraceParser::ParseTypedFtraceToRaw(
+    uint32_t ftrace_id,
+    int64_t ts,
+    uint32_t cpu,
+    uint32_t tid,
+    ConstBytes blob,
+    PacketSequenceStateGeneration* seq_state) {
   if (PERFETTO_UNLIKELY(!context_->config.ingest_ftrace_in_raw_table))
     return;
 
@@ -526,14 +588,42 @@
   auto inserter = context_->args_tracker->AddArgsTo(id);
 
   for (auto fld = decoder.ReadField(); fld.valid(); fld = decoder.ReadField()) {
-    if (PERFETTO_UNLIKELY(fld.id() >= kMaxFtraceEventFields)) {
+    uint16_t field_id = fld.id();
+    if (PERFETTO_UNLIKELY(field_id >= kMaxFtraceEventFields)) {
       PERFETTO_DLOG(
           "Skipping ftrace arg - proto field id is too large (%" PRIu16 ")",
-          fld.id());
+          field_id);
       continue;
     }
-    ProtoSchemaType type = m->fields[fld.id()].type;
-    StringId name_id = message_strings.field_name_ids[fld.id()];
+
+    ProtoSchemaType type = m->fields[field_id].type;
+    StringId name_id = message_strings.field_name_ids[field_id];
+
+    // Check if this field represents a kernel function.
+    auto it = std::find_if(
+        kKernelFunctionFields.begin(), kKernelFunctionFields.end(),
+        [ftrace_id, field_id](const FtraceEventAndFieldId& ev) {
+          return ev.event_id == ftrace_id && ev.field_id == field_id;
+        });
+    if (it != kKernelFunctionFields.end()) {
+      PERFETTO_CHECK(type == ProtoSchemaType::kUint64);
+
+      auto* interned_string = seq_state->LookupInternedMessage<
+          protos::pbzero::InternedData::kKernelSymbolsFieldNumber,
+          protos::pbzero::InternedString>(fld.as_uint64());
+
+      // If we don't have the string for this field (can happen if symbolization
+      // wasn't enabled, if reading the symbols errored out or on legacy traces)
+      // then just add the field as a normal arg.
+      if (interned_string) {
+        protozero::ConstBytes str = interned_string->str();
+        StringId str_id = context_->storage->InternString(base::StringView(
+            reinterpret_cast<const char*>(str.data), str.size));
+        inserter.AddArg(name_id, Variadic::String(str_id));
+        continue;
+      }
+    }
+
     switch (type) {
       case ProtoSchemaType::kInt32:
       case ProtoSchemaType::kInt64:
@@ -667,11 +757,43 @@
   }
 
   uint32_t tgid = static_cast<uint32_t>(evt.pid());
-  SystraceParser::GetOrCreate(context_)->ParseSdeTracingMarkWrite(
+  SystraceParser::GetOrCreate(context_)->ParseTracingMarkWrite(
       ts, pid, static_cast<char>(evt.trace_type()), evt.trace_begin(),
       evt.trace_name(), tgid, evt.value());
 }
 
+void FtraceParser::ParseDpuTracingMarkWrite(int64_t ts,
+                                            uint32_t pid,
+                                            ConstBytes blob) {
+  protos::pbzero::DpuTracingMarkWriteFtraceEvent::Decoder evt(blob.data,
+                                                              blob.size);
+  if (!evt.type()) {
+    context_->storage->IncrementStats(stats::systrace_parse_failure);
+    return;
+  }
+
+  uint32_t tgid = static_cast<uint32_t>(evt.pid());
+  SystraceParser::GetOrCreate(context_)->ParseTracingMarkWrite(
+      ts, pid, static_cast<char>(evt.type()), false /*trace_begin*/, evt.name(),
+      tgid, evt.value());
+}
+
+void FtraceParser::ParseG2dTracingMarkWrite(int64_t ts,
+                                            uint32_t pid,
+                                            ConstBytes blob) {
+  protos::pbzero::G2dTracingMarkWriteFtraceEvent::Decoder evt(blob.data,
+                                                              blob.size);
+  if (!evt.type()) {
+    context_->storage->IncrementStats(stats::systrace_parse_failure);
+    return;
+  }
+
+  uint32_t tgid = static_cast<uint32_t>(evt.pid());
+  SystraceParser::GetOrCreate(context_)->ParseTracingMarkWrite(
+      ts, pid, static_cast<char>(evt.type()), false /*trace_begin*/, evt.name(),
+      tgid, evt.value());
+}
+
 /** Parses ion heap events present in Pixel kernels. */
 void FtraceParser::ParseIonHeapGrowOrShrink(int64_t ts,
                                             uint32_t pid,
@@ -1023,15 +1145,29 @@
   context_->slice_tracker->End(timestamp, track_id);
 }
 
-void FtraceParser::ParseWorkqueueExecuteStart(int64_t timestamp,
-                                              uint32_t pid,
-                                              ConstBytes blob) {
+void FtraceParser::ParseWorkqueueExecuteStart(
+    int64_t timestamp,
+    uint32_t pid,
+    ConstBytes blob,
+    PacketSequenceStateGeneration* seq_state) {
   protos::pbzero::WorkqueueExecuteStartFtraceEvent::Decoder evt(blob.data,
                                                                 blob.size);
-  char slice_name[255];
-  snprintf(slice_name, sizeof(slice_name), "%#" PRIx64, evt.function());
-  StringId name_id =
-      context_->storage->InternString(base::StringView(slice_name));
+
+  auto* interned_string = seq_state->LookupInternedMessage<
+      protos::pbzero::InternedData::kKernelSymbolsFieldNumber,
+      protos::pbzero::InternedString>(static_cast<uint32_t>(evt.function()));
+  StringId name_id;
+  if (interned_string) {
+    protozero::ConstBytes str = interned_string->str();
+    name_id = context_->storage->InternString(
+        base::StringView(reinterpret_cast<const char*>(str.data), str.size));
+  } else {
+    char slice_name[255];
+    snprintf(slice_name, base::ArraySize(slice_name), "%#" PRIx64,
+             evt.function());
+    name_id = context_->storage->InternString(base::StringView(slice_name));
+  }
+
   UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
   TrackId track = context_->track_tracker->InternThreadTrack(utid);
   context_->slice_tracker->Begin(timestamp, track, workqueue_id_, name_id);
@@ -1119,19 +1255,23 @@
   protos::pbzero::GpuMemTotalFtraceEvent::Decoder gpu_mem_total(data.data,
                                                                 data.size);
 
+  TrackId track = kInvalidTrackId;
   const uint32_t pid = gpu_mem_total.pid();
   if (pid == 0) {
     // Pid 0 is used to indicate the global total
-    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        gpu_mem_total_global_id_);
-    context_->event_tracker->PushCounter(
-        ts, static_cast<double>(gpu_mem_total.size()), track);
+    track = context_->track_tracker->InternGlobalCounterTrack(
+        gpu_mem_total_name_id_, gpu_mem_total_unit_id_,
+        gpu_mem_total_global_desc_id_);
   } else {
-    UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
-    context_->event_tracker->PushProcessCounterForThread(
-        ts, static_cast<double>(gpu_mem_total.size()),
-        gpu_mem_total_process_id_, utid);
+    // Process emitting the packet can be different from the pid in the event.
+    UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
+    UniquePid upid = context_->storage->thread_table().upid()[utid].value_or(0);
+    track = context_->track_tracker->InternProcessCounterTrack(
+        gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
+        gpu_mem_total_proc_desc_id_);
   }
+  context_->event_tracker->PushCounter(
+      ts, static_cast<double>(gpu_mem_total.size()), track);
 }
 
 void FtraceParser::ParseThermalTemperature(int64_t timestamp,
@@ -1160,5 +1300,51 @@
       timestamp, static_cast<double>(evt.target()), track);
 }
 
+void FtraceParser::ParseSchedBlockedReason(
+    int64_t timestamp,
+    protozero::ConstBytes blob,
+    PacketSequenceStateGeneration* seq_state) {
+  protos::pbzero::SchedBlockedReasonFtraceEvent::Decoder evt(blob);
+  uint32_t pid = static_cast<uint32_t>(evt.pid());
+  auto utid = context_->process_tracker->GetOrCreateThread(pid);
+  InstantId id = context_->event_tracker->PushInstant(
+      timestamp, sched_blocked_reason_id_, utid, RefType::kRefUtid, false);
+
+  auto inserter = context_->args_tracker->AddArgsTo(id);
+  inserter.AddArg(io_wait_id_, Variadic::Boolean(evt.io_wait()));
+
+  uint32_t caller_iid = static_cast<uint32_t>(evt.caller());
+  auto* interned_string = seq_state->LookupInternedMessage<
+      protos::pbzero::InternedData::kKernelSymbolsFieldNumber,
+      protos::pbzero::InternedString>(caller_iid);
+
+  if (interned_string) {
+    protozero::ConstBytes str = interned_string->str();
+    StringId str_id = context_->storage->InternString(
+        base::StringView(reinterpret_cast<const char*>(str.data), str.size));
+    inserter.AddArg(function_id_, Variadic::String(str_id));
+  }
+}
+
+void FtraceParser::ParseFastRpcDmaStat(int64_t timestamp,
+                                       uint32_t pid,
+                                       protozero::ConstBytes blob) {
+  protos::pbzero::FastrpcDmaStatFtraceEvent::Decoder evt(blob.data, blob.size);
+
+  StringId name;
+  if (0 <= evt.cid() && evt.cid() < static_cast<int32_t>(kFastRpcCounterSize)) {
+    name = fast_rpc_counter_names_[static_cast<size_t>(evt.cid())];
+  } else {
+    char str[64];
+    sprintf(str, "mem.fastrpc[%" PRId32 "]", evt.cid());
+    name = context_->storage->InternString(str);
+  }
+
+  UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+  TrackId track = context_->track_tracker->InternThreadCounterTrack(name, utid);
+  context_->event_tracker->PushCounter(
+      timestamp, static_cast<double>(evt.total_allocated()), track);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index d33097f..5521d48 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -46,7 +46,8 @@
                              int64_t timestamp,
                              uint32_t cpu,
                              uint32_t pid,
-                             protozero::ConstBytes);
+                             protozero::ConstBytes,
+                             PacketSequenceStateGeneration*);
   void ParseSchedSwitch(uint32_t cpu, int64_t timestamp, protozero::ConstBytes);
   void ParseSchedWakeup(int64_t timestamp, protozero::ConstBytes);
   void ParseSchedWaking(int64_t timestamp, protozero::ConstBytes);
@@ -59,6 +60,12 @@
   void ParseSdeTracingMarkWrite(int64_t timestamp,
                                 uint32_t pid,
                                 protozero::ConstBytes);
+  void ParseDpuTracingMarkWrite(int64_t timestamp,
+                                uint32_t pid,
+                                protozero::ConstBytes);
+  void ParseG2dTracingMarkWrite(int64_t timestamp,
+                                uint32_t pid,
+                                protozero::ConstBytes);
   void ParseIonHeapGrowOrShrink(int64_t ts,
                                 uint32_t pid,
                                 protozero::ConstBytes,
@@ -107,7 +114,8 @@
   void ParseScmCallEnd(int64_t timestamp, uint32_t pid, protozero::ConstBytes);
   void ParseWorkqueueExecuteStart(int64_t timestamp,
                                   uint32_t pid,
-                                  protozero::ConstBytes);
+                                  protozero::ConstBytes,
+                                  PacketSequenceStateGeneration* seq_state);
   void ParseWorkqueueExecuteEnd(int64_t timestamp,
                                 uint32_t pid,
                                 protozero::ConstBytes);
@@ -124,6 +132,13 @@
   void ParseGpuMemTotal(int64_t timestamp, protozero::ConstBytes);
   void ParseThermalTemperature(int64_t timestamp, protozero::ConstBytes);
   void ParseCdevUpdate(int64_t timestamp, protozero::ConstBytes);
+  void ParseSchedBlockedReason(int64_t timestamp,
+                               protozero::ConstBytes,
+                               PacketSequenceStateGeneration*);
+  void ParseFastRpcDmaStat(int64_t timestamp,
+                           uint32_t pid,
+                           protozero::ConstBytes);
+
   TraceProcessorContext* context_;
   RssStatTracker rss_stat_tracker_;
 
@@ -147,8 +162,13 @@
   const StringId irq_id_;
   const StringId ret_arg_id_;
   const StringId vec_arg_id_;
-  const StringId gpu_mem_total_process_id_;
-  const StringId gpu_mem_total_global_id_;
+  const StringId gpu_mem_total_name_id_;
+  const StringId gpu_mem_total_unit_id_;
+  const StringId gpu_mem_total_global_desc_id_;
+  const StringId gpu_mem_total_proc_desc_id_;
+  const StringId sched_blocked_reason_id_;
+  const StringId io_wait_id_;
+  const StringId function_id_;
 
   struct FtraceMessageStrings {
     // The string id of name of the event field (e.g. sched_switch's id).
@@ -167,6 +187,9 @@
     StringId avg_lat = kNullStringId;
   };
 
+  static constexpr size_t kFastRpcCounterSize = 4;
+  std::array<StringId, kFastRpcCounterSize> fast_rpc_counter_names_;
+
   // Keep kMmEventCounterSize equal to mm_event_type::MM_TYPE_NUM in the kernel.
   static constexpr size_t kMmEventCounterSize = 7;
   std::array<MmEventCounterNames, kMmEventCounterSize> mm_event_counter_names_;
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index d196df2..7f3a6be 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -35,7 +35,8 @@
 using protozero::proto_utils::ParseVarInt;
 
 PERFETTO_ALWAYS_INLINE
-void FtraceTokenizer::TokenizeFtraceBundle(TraceBlobView bundle) {
+void FtraceTokenizer::TokenizeFtraceBundle(TraceBlobView bundle,
+                                           PacketSequenceState* state) {
   protos::pbzero::FtraceEventBundle::Decoder decoder(bundle.data(),
                                                      bundle.length());
 
@@ -59,13 +60,15 @@
   for (auto it = decoder.event(); it; ++it) {
     protozero::ConstBytes event = *it;
     size_t off = bundle.offset_of(event.data);
-    TokenizeFtraceEvent(cpu, bundle.slice(off, event.size));
+    TokenizeFtraceEvent(cpu, bundle.slice(off, event.size), state);
   }
   context_->sorter->FinalizeFtraceEventBatch(cpu);
 }
 
 PERFETTO_ALWAYS_INLINE
-void FtraceTokenizer::TokenizeFtraceEvent(uint32_t cpu, TraceBlobView event) {
+void FtraceTokenizer::TokenizeFtraceEvent(uint32_t cpu,
+                                          TraceBlobView event,
+                                          PacketSequenceState* state) {
   constexpr auto kTimestampFieldNumber =
       protos::pbzero::FtraceEvent::kTimestampFieldNumber;
   const uint8_t* data = event.data();
@@ -96,11 +99,10 @@
     return;
   }
 
-  int64_t timestamp = static_cast<int64_t>(raw_timestamp);
-
   // We don't need to parse this packet, just push it to be sorted with
   // the timestamp.
-  context_->sorter->PushFtraceEvent(cpu, timestamp, std::move(event));
+  int64_t timestamp = static_cast<int64_t>(raw_timestamp);
+  context_->sorter->PushFtraceEvent(cpu, timestamp, std::move(event), state);
 }
 
 PERFETTO_ALWAYS_INLINE
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
index e48cb74..7e8248b 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.h
@@ -25,15 +25,19 @@
 namespace perfetto {
 namespace trace_processor {
 
+class PacketSequenceState;
+
 class FtraceTokenizer {
  public:
   explicit FtraceTokenizer(TraceProcessorContext* context)
       : context_(context) {}
 
-  void TokenizeFtraceBundle(TraceBlobView bundle);
+  void TokenizeFtraceBundle(TraceBlobView bundle, PacketSequenceState*);
 
  private:
-  void TokenizeFtraceEvent(uint32_t cpu, TraceBlobView event);
+  void TokenizeFtraceEvent(uint32_t cpu,
+                           TraceBlobView event,
+                           PacketSequenceState*);
   void TokenizeFtraceCompactSched(uint32_t cpu,
                                   const uint8_t* data,
                                   size_t size);
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
index c1c5361..5e61475 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
@@ -18,6 +18,7 @@
 
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
@@ -38,6 +39,9 @@
 constexpr uint32_t kAsyncBegin = 5;
 constexpr uint32_t kAsyncInstant = 6;
 constexpr uint32_t kAsyncEnd = 7;
+constexpr uint32_t kFlowBegin = 8;
+constexpr uint32_t kFlowStep = 9;
+constexpr uint32_t kFlowEnd = 10;
 
 // Argument Types
 constexpr uint32_t kNull = 0;
@@ -245,6 +249,13 @@
         args.push_back(arg);
         cursor.SetWordIndex(arg_base + arg_size_words);
       }
+      auto insert_args = [this, args](ArgsTracker::BoundInserter* inserter) {
+        for (const Arg& arg : args) {
+          inserter->AddArg(
+              arg.name, arg.name,
+              arg.value.ToStorageVariadic(context_->storage.get()));
+        }
+      };
 
       switch (event_type) {
         case kInstant: {
@@ -322,7 +333,7 @@
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
           TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-          slices->Begin(ts, track_id, cat, name);
+          slices->Begin(ts, track_id, cat, name, insert_args);
           break;
         }
         case kDurationEnd: {
@@ -333,7 +344,7 @@
           // TODO(b/131181693): |cat| and |name| are not passed here so that
           // if two slices end at the same timestep, the slices get closed in
           // the correct order regardless of which end event is processed first.
-          slices->End(ts, track_id);
+          slices->End(ts, track_id, {}, {}, insert_args);
           break;
         }
         case kDurationComplete: {
@@ -351,7 +362,7 @@
               procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
                                   static_cast<uint32_t>(tinfo.pid));
           TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
-          slices->Scoped(ts, track_id, cat, name, duration);
+          slices->Scoped(ts, track_id, cat, name, duration, insert_args);
           break;
         }
         case kAsyncBegin: {
@@ -360,9 +371,11 @@
             context_->storage->IncrementStats(stats::fuchsia_invalid_event);
             return;
           }
+          UniquePid upid =
+              procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
-              name, correlation_id);
-          slices->Begin(ts, track_id, cat, name);
+              name, upid, correlation_id);
+          slices->Begin(ts, track_id, cat, name, insert_args);
           break;
         }
         case kAsyncInstant: {
@@ -374,8 +387,10 @@
             context_->storage->IncrementStats(stats::fuchsia_invalid_event);
             return;
           }
+          UniquePid upid =
+              procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
-              name, correlation_id);
+              name, upid, correlation_id);
           InstantId id = context_->event_tracker->PushInstant(
               ts, name, track_id.value, RefType::kRefTrack);
           auto inserter = context_->args_tracker->AddArgsTo(id);
@@ -393,9 +408,50 @@
             context_->storage->IncrementStats(stats::fuchsia_invalid_event);
             return;
           }
+          UniquePid upid =
+              procs->GetOrCreateProcess(static_cast<uint32_t>(tinfo.pid));
           TrackId track_id = context_->track_tracker->InternFuchsiaAsyncTrack(
-              name, correlation_id);
-          slices->End(ts, track_id, cat, name);
+              name, upid, correlation_id);
+          slices->End(ts, track_id, cat, name, insert_args);
+          break;
+        }
+        case kFlowBegin: {
+          uint64_t correlation_id;
+          if (!cursor.ReadUint64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
+          UniqueTid utid =
+              procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
+                                  static_cast<uint32_t>(tinfo.pid));
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          context_->flow_tracker->Begin(track_id, correlation_id);
+          break;
+        }
+        case kFlowStep: {
+          uint64_t correlation_id;
+          if (!cursor.ReadUint64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
+          UniqueTid utid =
+              procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
+                                  static_cast<uint32_t>(tinfo.pid));
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          context_->flow_tracker->Step(track_id, correlation_id);
+          break;
+        }
+        case kFlowEnd: {
+          uint64_t correlation_id;
+          if (!cursor.ReadUint64(&correlation_id)) {
+            context_->storage->IncrementStats(stats::fuchsia_invalid_event);
+            return;
+          }
+          UniqueTid utid =
+              procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
+                                  static_cast<uint32_t>(tinfo.pid));
+          TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+          context_->flow_tracker->End(track_id, correlation_id, true, true);
           break;
         }
       }
diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.cc b/src/trace_processor/importers/gzip/gzip_trace_parser.cc
index 89e8c1d..bc42db3 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.cc
+++ b/src/trace_processor/importers/gzip/gzip_trace_parser.cc
@@ -22,26 +22,43 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
 namespace trace_processor {
 
+namespace {
+
+using ResultCode = GzipDecompressor::ResultCode;
+
+}  // namespace
+
 GzipTraceParser::GzipTraceParser(TraceProcessorContext* context)
     : context_(context) {}
 
+GzipTraceParser::GzipTraceParser(std::unique_ptr<ChunkedTraceReader> reader)
+    : context_(nullptr), inner_(std::move(reader)) {}
+
 GzipTraceParser::~GzipTraceParser() = default;
 
 util::Status GzipTraceParser::Parse(std::unique_ptr<uint8_t[]> data,
                                     size_t size) {
-  uint8_t* start = data.get();
+  return ParseUnowned(data.get(), size);
+}
+
+util::Status GzipTraceParser::ParseUnowned(const uint8_t* data, size_t size) {
+  const uint8_t* start = data;
   size_t len = size;
 
   if (!inner_) {
+    PERFETTO_CHECK(context_);
     inner_.reset(new ForwardingTraceParser(context_));
+  }
 
+  if (!first_chunk_parsed_) {
     // .ctrace files begin with: "TRACE:\n" or "done. TRACE:\n" strip this if
     // present.
-    base::StringView beginning(reinterpret_cast<char*>(start), size);
+    base::StringView beginning(reinterpret_cast<const char*>(start), size);
 
     static const char* kSystraceFileHeader = "TRACE:\n";
     size_t offset = Find(kSystraceFileHeader, beginning);
@@ -49,33 +66,49 @@
       start += strlen(kSystraceFileHeader) + offset;
       len -= strlen(kSystraceFileHeader) + offset;
     }
+    first_chunk_parsed_ = true;
   }
-  decompressor_.SetInput(start, len);
 
   // Our default uncompressed buffer size is 32MB as it allows for good
   // throughput.
-  using ResultCode = GzipDecompressor::ResultCode;
   constexpr size_t kUncompressedBufferSize = 32 * 1024 * 1024;
 
+  needs_more_input_ = false;
+  decompressor_.SetInput(start, len);
+
   for (auto ret = ResultCode::kOk; ret != ResultCode::kEof;) {
-    std::unique_ptr<uint8_t[]> buffer(new uint8_t[kUncompressedBufferSize]);
+    if (!buffer_) {
+      buffer_.reset(new uint8_t[kUncompressedBufferSize]);
+      bytes_written_ = 0;
+    }
+
     auto result =
-        decompressor_.Decompress(buffer.get(), kUncompressedBufferSize);
+        decompressor_.Decompress(buffer_.get() + bytes_written_,
+                                 kUncompressedBufferSize - bytes_written_);
     ret = result.ret;
     if (ret == ResultCode::kError || ret == ResultCode::kNoProgress)
-      return util::ErrStatus("Unable to decompress gzip/ctrace trace");
-    if (ret == ResultCode::kNeedsMoreInput)
-      break;
+      return util::ErrStatus("Failed to decompress trace chunk");
 
-    util::Status status =
-        inner_->Parse(std::move(buffer), result.bytes_written);
-    if (!status.ok())
-      return status;
+    if (ret == ResultCode::kNeedsMoreInput) {
+      PERFETTO_DCHECK(result.bytes_written == 0);
+      needs_more_input_ = true;
+      return util::OkStatus();
+    }
+    bytes_written_ += result.bytes_written;
+
+    if (bytes_written_ == kUncompressedBufferSize || ret == ResultCode::kEof)
+      RETURN_IF_ERROR(inner_->Parse(std::move(buffer_), bytes_written_));
   }
   return util::OkStatus();
 }
 
-void GzipTraceParser::NotifyEndOfFile() {}
+void GzipTraceParser::NotifyEndOfFile() {
+  // TODO(lalitm): this should really be an error returned to the caller but
+  // due to historical implementation, NotifyEndOfFile does not return a
+  // util::Status.
+  PERFETTO_DCHECK(!needs_more_input_);
+  PERFETTO_DCHECK(!buffer_);
+}
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/gzip/gzip_trace_parser.h b/src/trace_processor/importers/gzip/gzip_trace_parser.h
index c83416e..b56fae3 100644
--- a/src/trace_processor/importers/gzip/gzip_trace_parser.h
+++ b/src/trace_processor/importers/gzip/gzip_trace_parser.h
@@ -28,16 +28,27 @@
 class GzipTraceParser : public ChunkedTraceReader {
  public:
   explicit GzipTraceParser(TraceProcessorContext*);
+  explicit GzipTraceParser(std::unique_ptr<ChunkedTraceReader>);
   ~GzipTraceParser() override;
 
   // ChunkedTraceReader implementation
   util::Status Parse(std::unique_ptr<uint8_t[]>, size_t) override;
   void NotifyEndOfFile() override;
 
+  util::Status ParseUnowned(const uint8_t*, size_t);
+
+  bool needs_more_input() const { return needs_more_input_; }
+
  private:
   TraceProcessorContext* const context_;
   GzipDecompressor decompressor_;
   std::unique_ptr<ChunkedTraceReader> inner_;
+
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t bytes_written_ = 0;
+
+  bool first_chunk_parsed_ = false;
+  bool needs_more_input_ = false;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/json/json_trace_parser.cc b/src/trace_processor/importers/json/json_trace_parser.cc
index d58df09..705c55e 100644
--- a/src/trace_processor/importers/json/json_trace_parser.cc
+++ b/src/trace_processor/importers/json/json_trace_parser.cc
@@ -22,8 +22,10 @@
 #include <string>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
@@ -34,6 +36,26 @@
 namespace perfetto {
 namespace trace_processor {
 
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+namespace {
+
+base::Optional<uint64_t> MaybeExtractFlowIdentifier(const Json::Value& value,
+                                                    bool version2) {
+  std::string id_key = (version2 ? "bind_id" : "id");
+  if (!value.isMember(id_key))
+    return base::nullopt;
+  auto id = value[id_key];
+  if (id.isNumeric())
+    return id.asUInt64();
+  if (!id.isString())
+    return base::nullopt;
+  const char* c_string = id.asCString();
+  return base::CStringToUInt64(c_string, 16);
+}
+
+}  // namespace
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
 JsonTraceParser::JsonTraceParser(TraceProcessorContext* context)
     : context_(context), systrace_line_parser_(context) {}
 
@@ -62,6 +84,7 @@
   ProcessTracker* procs = context_->process_tracker.get();
   TraceStorage* storage = context_->storage.get();
   SliceTracker* slice_tracker = context_->slice_tracker.get();
+  FlowTracker* flow_tracker = context_->flow_tracker.get();
 
   auto& ph = value["ph"];
   if (!ph.isString())
@@ -101,6 +124,7 @@
     case 'B': {  // TRACE_EVENT_BEGIN.
       TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
       slice_tracker->Begin(timestamp, track_id, cat_id, name_id, args_inserter);
+      MaybeAddFlow(track_id, value);
       break;
     }
     case 'E': {  // TRACE_EVENT_END.
@@ -116,6 +140,49 @@
       TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
       slice_tracker->Scoped(timestamp, track_id, cat_id, name_id,
                             opt_dur.value(), args_inserter);
+      MaybeAddFlow(track_id, value);
+      break;
+    }
+    case 's': {  // TRACE_EVENT_FLOW_START
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      auto opt_source_id =
+          MaybeExtractFlowIdentifier(value, /* version2 = */ false);
+      if (opt_source_id) {
+        FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+            opt_source_id.value(), cat_id, name_id);
+        flow_tracker->Begin(track_id, flow_id);
+      } else {
+        context_->storage->IncrementStats(stats::flow_invalid_id);
+      }
+      break;
+    }
+    case 't': {  // TRACE_EVENT_FLOW_STEP
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      auto opt_source_id =
+          MaybeExtractFlowIdentifier(value, /* version2 = */ false);
+      if (opt_source_id) {
+        FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+            opt_source_id.value(), cat_id, name_id);
+        flow_tracker->Step(track_id, flow_id);
+      } else {
+        context_->storage->IncrementStats(stats::flow_invalid_id);
+      }
+      break;
+    }
+    case 'f': {  // TRACE_EVENT_FLOW_END
+      TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+      auto opt_source_id =
+          MaybeExtractFlowIdentifier(value, /* version2 = */ false);
+      if (opt_source_id) {
+        FlowId flow_id = flow_tracker->GetFlowIdForV1Event(
+            opt_source_id.value(), cat_id, name_id);
+        bool bind_enclosing_slice =
+            value.isMember("bp") && strcmp(value["bp"].asCString(), "e") == 0;
+        flow_tracker->End(track_id, flow_id, bind_enclosing_slice,
+                          /* close_flow = */ false);
+      } else {
+        context_->storage->IncrementStats(stats::flow_invalid_id);
+      }
       break;
     }
     case 'M': {  // Metadata events (process and thread names).
@@ -144,6 +211,29 @@
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
 }
 
+void JsonTraceParser::MaybeAddFlow(TrackId track_id, const Json::Value& event) {
+  PERFETTO_DCHECK(json::IsJsonSupported());
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+  auto opt_bind_id = MaybeExtractFlowIdentifier(event, /* version2 = */ true);
+  if (opt_bind_id) {
+    FlowTracker* flow_tracker = context_->flow_tracker.get();
+    bool flow_out = event.isMember("flow_out") && event["flow_out"].asBool();
+    bool flow_in = event.isMember("flow_in") && event["flow_in"].asBool();
+    if (flow_in && flow_out) {
+      flow_tracker->Step(track_id, opt_bind_id.value());
+    } else if (flow_out) {
+      flow_tracker->Begin(track_id, opt_bind_id.value());
+    } else if (flow_in) {
+      // bind_enclosing_slice is always true for v2 flow events
+      flow_tracker->End(track_id, opt_bind_id.value(), true,
+                        /* close_flow = */ false);
+    } else {
+      context_->storage->IncrementStats(stats::flow_without_direction);
+    }
+  }
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/importers/json/json_trace_parser.h b/src/trace_processor/importers/json/json_trace_parser.h
index b2f7cc4..d145953 100644
--- a/src/trace_processor/importers/json/json_trace_parser.h
+++ b/src/trace_processor/importers/json/json_trace_parser.h
@@ -51,6 +51,8 @@
  private:
   TraceProcessorContext* const context_;
   SystraceLineParser systrace_line_parser_;
+
+  void MaybeAddFlow(TrackId track_id, const Json::Value& event);
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/json/json_trace_tokenizer.cc b/src/trace_processor/importers/json/json_trace_tokenizer.cc
index 004f3d7..2008699 100644
--- a/src/trace_processor/importers/json/json_trace_tokenizer.cc
+++ b/src/trace_processor/importers/json/json_trace_tokenizer.cc
@@ -381,6 +381,16 @@
       } else if (key == "metadata") {
         position_ = TracePosition::kWaitingForMetadataDictionary;
         return ParseInternal(next + 1, end, out);
+      } else if (key == "displayTimeUnit") {
+        std::string time_unit;
+        auto string_res = ReadOneJsonString(next + 1, end, &time_unit, &next);
+        if (string_res == ReadStringRes::kFatalError)
+          return util::ErrStatus("Could not parse displayTimeUnit");
+        if (string_res == ReadStringRes::kNeedsMoreData)
+          return util::ErrStatus("displayTimeUnit too large");
+        if (time_unit != "ms" && time_unit != "ns")
+          return util::ErrStatus("displayTimeUnit unknown");
+        return ParseInternal(next, end, out);
       } else {
         // If we don't recognize the key, just ignore the rest of the trace and
         // go to EOF.
diff --git a/src/trace_processor/importers/json/json_tracker.h b/src/trace_processor/importers/json/json_tracker.h
index ac24083..d786bf1 100644
--- a/src/trace_processor/importers/json/json_tracker.h
+++ b/src/trace_processor/importers/json/json_tracker.h
@@ -33,7 +33,7 @@
   JsonTracker(const JsonTracker&) = delete;
   JsonTracker& operator=(const JsonTracker&) = delete;
   explicit JsonTracker(TraceProcessorContext*);
-  virtual ~JsonTracker();
+  ~JsonTracker() override;
 
   static JsonTracker* GetOrCreate(TraceProcessorContext* context) {
     if (!context->json_tracker) {
diff --git a/src/trace_processor/importers/memory_tracker/BUILD.gn b/src/trace_processor/importers/memory_tracker/BUILD.gn
new file mode 100644
index 0000000..4ac6669
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/BUILD.gn
@@ -0,0 +1,31 @@
+# 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.
+
+import("../../../../gn/perfetto.gni")
+
+source_set("graph_processor") {
+  deps = [ "../../../../gn:default_deps" ]
+  public_deps = [
+    "../../../../include/perfetto/base",
+    "../../../../include/perfetto/ext/base",
+    "../../../../include/perfetto/ext/trace_processor/importers/memory_tracker",
+  ]
+  sources = [
+    "graph.cc",
+    "graph_processor.cc",
+    "memory_allocator_node_id.cc",
+    "raw_memory_graph_node.cc",
+    "raw_process_memory_node.cc",
+  ]
+}
diff --git a/src/trace_processor/importers/memory_tracker/graph.cc b/src/trace_processor/importers/memory_tracker/graph.cc
new file mode 100644
index 0000000..d156da6
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/graph.cc
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+using Edge = GlobalNodeGraph::Edge;
+using PostOrderIterator = GlobalNodeGraph::PostOrderIterator;
+using PreOrderIterator = GlobalNodeGraph::PreOrderIterator;
+using Process = GlobalNodeGraph::Process;
+using Node = GlobalNodeGraph::Node;
+using perfetto::base::SplitString;
+
+}  // namespace
+
+GlobalNodeGraph::GlobalNodeGraph()
+    : shared_memory_graph_(
+          std::unique_ptr<Process>(new Process(kNullProcessId, this))) {}
+GlobalNodeGraph::~GlobalNodeGraph() {}
+
+Process* GlobalNodeGraph::CreateGraphForProcess(
+    base::PlatformProcessId process_id) {
+  auto id_to_node_iterator = process_node_graphs_.emplace(
+      process_id, std::unique_ptr<Process>(new Process(process_id, this)));
+  return id_to_node_iterator.first->second.get();
+}
+
+void GlobalNodeGraph::AddNodeOwnershipEdge(Node* owner,
+                                           Node* owned,
+                                           int importance) {
+  all_edges_.emplace_front(owner, owned, importance);
+  Edge* edge = &*all_edges_.begin();
+  owner->SetOwnsEdge(edge);
+  owned->AddOwnedByEdge(edge);
+}
+
+Node* GlobalNodeGraph::CreateNode(Process* process_graph, Node* parent) {
+  all_nodes_.emplace_front(process_graph, parent);
+  return &*all_nodes_.begin();
+}
+
+PreOrderIterator GlobalNodeGraph::VisitInDepthFirstPreOrder() {
+  std::vector<Node*> roots;
+  for (auto it = process_node_graphs_.rbegin();
+       it != process_node_graphs_.rend(); it++) {
+    roots.push_back(it->second->root());
+  }
+  roots.push_back(shared_memory_graph_->root());
+  return PreOrderIterator{std::move(roots)};
+}
+
+PostOrderIterator GlobalNodeGraph::VisitInDepthFirstPostOrder() {
+  std::vector<Node*> roots;
+  for (auto it = process_node_graphs_.rbegin();
+       it != process_node_graphs_.rend(); it++) {
+    roots.push_back(it->second->root());
+  }
+  roots.push_back(shared_memory_graph_->root());
+  return PostOrderIterator(std::move(roots));
+}
+
+Process::Process(base::PlatformProcessId pid, GlobalNodeGraph* global_graph)
+    : pid_(pid),
+      global_graph_(global_graph),
+      root_(global_graph->CreateNode(this, nullptr)) {}
+Process::~Process() {}
+
+Node* Process::CreateNode(MemoryAllocatorNodeId id,
+                          const std::string& path,
+                          bool weak) {
+  auto tokens = base::SplitString(path, "/");
+
+  // Perform a tree traversal, creating the nodes if they do not
+  // already exist on the path to the child.
+  Node* current = root_;
+  for (const auto& key : tokens) {
+    Node* parent = current;
+    current = current->GetChild(key);
+    if (!current) {
+      current = global_graph_->CreateNode(this, parent);
+      parent->InsertChild(key, current);
+    }
+  }
+
+  // The final node should have the weakness specified by the
+  // argument and also be considered explicit.
+  current->set_weak(weak);
+  current->set_explicit(true);
+
+  // The final node should also have the associated |id|.
+  current->set_id(id);
+
+  // Add to the global id map as well if it exists.
+  if (!id.empty())
+    global_graph_->nodes_by_id_.emplace(id, current);
+
+  return current;
+}
+
+Node* Process::FindNode(const std::string& path) {
+  auto tokens = base::SplitString(path, "/");
+
+  Node* current = root_;
+  for (const auto& key : tokens) {
+    current = current->GetChild(key);
+    if (!current)
+      return nullptr;
+  }
+  return current;
+}
+
+Node::Node(Process* node_graph, Node* parent)
+    : node_graph_(node_graph), parent_(parent), owns_edge_(nullptr) {}
+Node::~Node() {}
+
+Node* Node::GetChild(const std::string& name) const {
+  auto child = children_.find(name);
+  return child == children_.end() ? nullptr : child->second;
+}
+
+void Node::InsertChild(const std::string& name, Node* node) {
+  PERFETTO_DCHECK(node);
+  children_.emplace(name, node);
+}
+
+Node* Node::CreateChild(const std::string& name) {
+  Node* new_child = node_graph_->global_graph()->CreateNode(node_graph_, this);
+  InsertChild(name, new_child);
+  return new_child;
+}
+
+bool Node::IsDescendentOf(const Node& possible_parent) const {
+  const Node* current = this;
+  while (current != nullptr) {
+    if (current == &possible_parent)
+      return true;
+    current = current->parent();
+  }
+  return false;
+}
+
+void Node::AddOwnedByEdge(Edge* edge) {
+  owned_by_edges_.push_back(edge);
+}
+
+void Node::SetOwnsEdge(Edge* owns_edge) {
+  owns_edge_ = owns_edge;
+}
+
+void Node::AddEntry(const std::string& name,
+                    Node::Entry::ScalarUnits units,
+                    uint64_t value) {
+  entries_.emplace(name, Node::Entry(units, value));
+}
+
+void Node::AddEntry(const std::string& name, const std::string& value) {
+  entries_.emplace(name, Node::Entry(value));
+}
+
+Node::Entry::Entry(Entry::ScalarUnits units2, uint64_t value)
+    : type(Node::Entry::Type::kUInt64), units(units2), value_uint64(value) {}
+
+Node::Entry::Entry(const std::string& value)
+    : type(Node::Entry::Type::kString),
+      units(Node::Entry::ScalarUnits::kObjects),
+      value_string(value),
+      value_uint64(0) {}
+
+Edge::Edge(Node* source, Node* target, int priority)
+    : source_(source), target_(target), priority_(priority) {}
+
+PreOrderIterator::PreOrderIterator(std::vector<Node*>&& roots)
+    : to_visit_(std::move(roots)) {}
+PreOrderIterator::PreOrderIterator(PreOrderIterator&& other) = default;
+PreOrderIterator::~PreOrderIterator() {}
+
+// Yields the next node in the DFS post-order traversal.
+Node* PreOrderIterator::next() {
+  while (!to_visit_.empty()) {
+    // Retain a pointer to the node at the top and remove it from stack.
+    Node* node = to_visit_.back();
+    to_visit_.pop_back();
+
+    // If the node has already been visited, don't visit it again.
+    if (visited_.count(node) != 0)
+      continue;
+
+    // If we haven't visited the node which this node owns then wait for that.
+    if (node->owns_edge() && visited_.count(node->owns_edge()->target()) == 0)
+      continue;
+
+    // If we haven't visited the node's parent then wait for that.
+    if (node->parent() && visited_.count(node->parent()) == 0)
+      continue;
+
+    // Visit all children of this node.
+    for (auto it = node->children()->rbegin(); it != node->children()->rend();
+         it++) {
+      to_visit_.push_back(it->second);
+    }
+
+    // Visit all owners of this node.
+    for (auto it = node->owned_by_edges()->rbegin();
+         it != node->owned_by_edges()->rend(); it++) {
+      to_visit_.push_back((*it)->source());
+    }
+
+    // Add this node to the visited set.
+    visited_.insert(node);
+    return node;
+  }
+  return nullptr;
+}
+
+PostOrderIterator::PostOrderIterator(std::vector<Node*>&& roots)
+    : to_visit_(std::move(roots)) {}
+PostOrderIterator::PostOrderIterator(PostOrderIterator&& other) = default;
+PostOrderIterator::~PostOrderIterator() = default;
+
+// Yields the next node in the DFS post-order traversal.
+Node* PostOrderIterator::next() {
+  while (!to_visit_.empty()) {
+    // Retain a pointer to the node at the top and remove it from stack.
+    Node* node = to_visit_.back();
+    to_visit_.pop_back();
+
+    // If the node has already been visited, don't visit it again.
+    if (visited_.count(node) != 0)
+      continue;
+
+    // If the node is at the top of the path, we have already looked
+    // at its children and owners.
+    if (!path_.empty() && path_.back() == node) {
+      // Mark the current node as visited so we don't visit again.
+      visited_.insert(node);
+
+      // The current node is no longer on the path.
+      path_.pop_back();
+
+      return node;
+    }
+
+    // If the node is not at the front, it should also certainly not be
+    // anywhere else in the path. If it is, there is a cycle in the graph.
+    path_.push_back(node);
+
+    // Add this node back to the queue of nodes to visit.
+    to_visit_.push_back(node);
+
+    // Visit all children of this node.
+    for (auto it = node->children()->rbegin(); it != node->children()->rend();
+         it++) {
+      to_visit_.push_back(it->second);
+    }
+
+    // Visit all owners of this node.
+    for (auto it = node->owned_by_edges()->rbegin();
+         it != node->owned_by_edges()->rend(); it++) {
+      to_visit_.push_back((*it)->source());
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/graph_processor.cc b/src/trace_processor/importers/memory_tracker/graph_processor.cc
new file mode 100644
index 0000000..3ab41ea
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/graph_processor.cc
@@ -0,0 +1,801 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"
+
+#include <list>
+
+namespace perfetto {
+namespace trace_processor {
+
+using Edge = GlobalNodeGraph::Edge;
+using Node = GlobalNodeGraph::Node;
+using Process = GlobalNodeGraph::Process;
+
+namespace {
+
+const char kSharedMemoryRootNode[] = "shared_memory";
+const char kSizeEntryName[] = "size";
+const char kEffectiveSizeEntryName[] = "effective_size";
+
+Node::Entry::ScalarUnits EntryUnitsFromString(const std::string& units) {
+  if (units == RawMemoryGraphNode::kUnitsBytes) {
+    return Node::Entry::ScalarUnits::kBytes;
+  } else if (units == RawMemoryGraphNode::kUnitsObjects) {
+    return Node::Entry::ScalarUnits::kObjects;
+  } else {
+    // Invalid units so we just return a value of the correct type.
+    return Node::Entry::ScalarUnits::kObjects;
+  }
+}
+
+base::Optional<uint64_t> GetSizeEntryOfNode(Node* node) {
+  auto size_it = node->entries()->find(kSizeEntryName);
+  if (size_it == node->entries()->end())
+    return base::nullopt;
+
+  PERFETTO_DCHECK(size_it->second.type == Node::Entry::Type::kUInt64);
+  PERFETTO_DCHECK(size_it->second.units == Node::Entry::ScalarUnits::kBytes);
+  return base::Optional<uint64_t>(size_it->second.value_uint64);
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<GlobalNodeGraph> GraphProcessor::CreateMemoryGraph(
+    const GraphProcessor::RawMemoryNodeMap& process_nodes) {
+  auto global_graph = std::unique_ptr<GlobalNodeGraph>(new GlobalNodeGraph());
+
+  // First pass: collects allocator nodes into a graph and populate
+  // with entries.
+  for (const auto& pid_to_node : process_nodes) {
+    // There can be null entries in the map; simply filter these out.
+    if (!pid_to_node.second)
+      continue;
+
+    auto* graph = global_graph->CreateGraphForProcess(pid_to_node.first);
+    CollectAllocatorNodes(*pid_to_node.second, global_graph.get(), graph);
+  }
+
+  // Second pass: generate the graph of edges between the nodes.
+  for (const auto& pid_to_node : process_nodes) {
+    // There can be null entries in the map; simply filter these out.
+    if (!pid_to_node.second)
+      continue;
+
+    AddEdges(*pid_to_node.second, global_graph.get());
+  }
+
+  return global_graph;
+}
+
+// static
+void GraphProcessor::RemoveWeakNodesFromGraph(GlobalNodeGraph* global_graph) {
+  auto* global_root = global_graph->shared_memory_graph()->root();
+
+  // Third pass: mark recursively nodes as weak if they don't have an associated
+  // node and all their children are weak.
+  MarkImplicitWeakParentsRecursively(global_root);
+  for (const auto& pid_to_process : global_graph->process_node_graphs()) {
+    MarkImplicitWeakParentsRecursively(pid_to_process.second->root());
+  }
+
+  // Fourth pass: recursively mark nodes as weak if they own a node which is
+  // weak or if they have a parent who is weak.
+  {
+    std::set<const Node*> visited;
+    MarkWeakOwnersAndChildrenRecursively(global_root, &visited);
+    for (const auto& pid_to_process : global_graph->process_node_graphs()) {
+      MarkWeakOwnersAndChildrenRecursively(pid_to_process.second->root(),
+                                           &visited);
+    }
+  }
+
+  // Fifth pass: remove all nodes which are weak (including their descendants)
+  // and clean owned by edges to match.
+  RemoveWeakNodesRecursively(global_root);
+  for (const auto& pid_to_process : global_graph->process_node_graphs()) {
+    RemoveWeakNodesRecursively(pid_to_process.second->root());
+  }
+}
+
+// static
+void GraphProcessor::AddOverheadsAndPropagateEntries(
+    GlobalNodeGraph* global_graph) {
+  // Sixth pass: account for tracing overhead in system memory allocators.
+  for (auto& pid_to_process : global_graph->process_node_graphs()) {
+    Process* process = pid_to_process.second.get();
+    if (process->FindNode("winheap")) {
+      AssignTracingOverhead("winheap", global_graph,
+                            pid_to_process.second.get());
+    } else if (process->FindNode("malloc")) {
+      AssignTracingOverhead("malloc", global_graph,
+                            pid_to_process.second.get());
+    }
+  }
+
+  // Seventh pass: aggregate non-size integer entries into parents and propagate
+  // string and int entries for shared graph.
+  auto* global_root = global_graph->shared_memory_graph()->root();
+  AggregateNumericsRecursively(global_root);
+  PropagateNumericsAndDiagnosticsRecursively(global_root);
+  for (auto& pid_to_process : global_graph->process_node_graphs()) {
+    AggregateNumericsRecursively(pid_to_process.second->root());
+  }
+}
+
+// static
+void GraphProcessor::CalculateSizesForGraph(GlobalNodeGraph* global_graph) {
+  // Eighth pass: calculate the size field for nodes by considering the sizes
+  // of their children and owners.
+  {
+    auto it = global_graph->VisitInDepthFirstPostOrder();
+    while (Node* node = it.next()) {
+      CalculateSizeForNode(node);
+    }
+  }
+
+  // Ninth pass: Calculate not-owned and not-owning sub-sizes of all nodes.
+  {
+    auto it = global_graph->VisitInDepthFirstPostOrder();
+    while (Node* node = it.next()) {
+      CalculateNodeSubSizes(node);
+    }
+  }
+
+  // Tenth pass: Calculate owned and owning coefficients of owned and owner
+  // nodes.
+  {
+    auto it = global_graph->VisitInDepthFirstPostOrder();
+    while (Node* node = it.next()) {
+      CalculateNodeOwnershipCoefficient(node);
+    }
+  }
+
+  // Eleventh pass: Calculate cumulative owned and owning coefficients of all
+  // nodes.
+  {
+    auto it = global_graph->VisitInDepthFirstPreOrder();
+    while (Node* node = it.next()) {
+      CalculateNodeCumulativeOwnershipCoefficient(node);
+    }
+  }
+
+  // Twelfth pass: Calculate the effective sizes of all nodes.
+  {
+    auto it = global_graph->VisitInDepthFirstPostOrder();
+    while (Node* node = it.next()) {
+      CalculateNodeEffectiveSize(node);
+    }
+  }
+}
+
+// static
+std::map<base::PlatformProcessId, uint64_t>
+GraphProcessor::ComputeSharedFootprintFromGraph(
+    const GlobalNodeGraph& global_graph) {
+  // Go through all nodes associated with global nodes and find if they are
+  // owned by shared memory nodes.
+  Node* global_root =
+      global_graph.shared_memory_graph()->root()->GetChild("global");
+
+  // If there are no global nodes then just return an empty map with no data.
+  if (!global_root)
+    return std::map<base::PlatformProcessId, uint64_t>();
+
+  struct GlobalNodeOwners {
+    std::list<Edge*> edges;
+    int max_priority = 0;
+  };
+
+  std::map<Node*, GlobalNodeOwners> global_node_to_shared_owners;
+  for (const auto& path_to_child : *global_root->children()) {
+    // The path of this node is something like "global/foo".
+    Node* global_node = path_to_child.second;
+
+    // If there's no size to attribute, there's no point in propagating
+    // anything.
+    if (global_node->entries()->count(kSizeEntryName) == 0)
+      continue;
+
+    for (auto* edge : *global_node->owned_by_edges()) {
+      // Find if the source node's path starts with "shared_memory/" which
+      // indcates shared memory.
+      Node* source_root = edge->source()->node_graph()->root();
+      const Node* current = edge->source();
+      PERFETTO_DCHECK(current != source_root);
+
+      // Traverse up until we hit the point where |current| holds a node which
+      // is the child of |source_root|.
+      while (current->parent() != source_root)
+        current = current->parent();
+
+      // If the source is indeed a shared memory node, add the edge to the map.
+      if (source_root->GetChild(kSharedMemoryRootNode) == current) {
+        GlobalNodeOwners* owners = &global_node_to_shared_owners[global_node];
+        owners->edges.push_back(edge);
+        owners->max_priority = std::max(owners->max_priority, edge->priority());
+      }
+    }
+  }
+
+  // Go through the map and leave only the edges which have the maximum
+  // priority.
+  for (auto& global_to_shared_edges : global_node_to_shared_owners) {
+    int max_priority = global_to_shared_edges.second.max_priority;
+    global_to_shared_edges.second.edges.remove_if(
+        [max_priority](Edge* edge) { return edge->priority() < max_priority; });
+  }
+
+  // Compute the footprints by distributing the memory of the nodes
+  // among the processes which have edges left.
+  std::map<base::PlatformProcessId, uint64_t> pid_to_shared_footprint;
+  for (const auto& global_to_shared_edges : global_node_to_shared_owners) {
+    Node* node = global_to_shared_edges.first;
+    const auto& edges = global_to_shared_edges.second.edges;
+
+    const Node::Entry& size_entry =
+        node->entries()->find(kSizeEntryName)->second;
+    PERFETTO_DCHECK(size_entry.type == Node::Entry::kUInt64);
+
+    uint64_t size_per_process = size_entry.value_uint64 / edges.size();
+    for (auto* edge : edges) {
+      base::PlatformProcessId pid = edge->source()->node_graph()->pid();
+      pid_to_shared_footprint[pid] += size_per_process;
+    }
+  }
+
+  return pid_to_shared_footprint;
+}
+
+// static
+void GraphProcessor::CollectAllocatorNodes(const RawProcessMemoryNode& source,
+                                           GlobalNodeGraph* global_graph,
+                                           Process* process_graph) {
+  // Turn each node into a node in the graph of nodes in the appropriate
+  // process node or global node.
+  for (const auto& path_to_node : source.allocator_nodes()) {
+    const std::string& path = path_to_node.first;
+    const RawMemoryGraphNode& raw_node = *path_to_node.second;
+
+    // All global nodes (i.e. those starting with global/) should be redirected
+    // to the shared graph.
+    bool is_global = base::StartsWith(path, "global/");
+    Process* process =
+        is_global ? global_graph->shared_memory_graph() : process_graph;
+
+    Node* node;
+    auto node_iterator = global_graph->nodes_by_id().find(raw_node.id());
+    if (node_iterator == global_graph->nodes_by_id().end()) {
+      // Storing whether the process is weak here will allow for later
+      // computations on whether or not the node should be removed.
+      bool is_weak = raw_node.flags() & RawMemoryGraphNode::Flags::kWeak;
+      node = process->CreateNode(raw_node.id(), path, is_weak);
+    } else {
+      node = node_iterator->second;
+
+      PERFETTO_DCHECK(node == process->FindNode(path));
+
+      PERFETTO_DCHECK(is_global);
+    }
+
+    // Copy any entries not already present into the node.
+    for (auto& entry : raw_node.entries()) {
+      switch (entry.entry_type) {
+        case RawMemoryGraphNode::MemoryNodeEntry::EntryType::kUint64:
+          node->AddEntry(entry.name, EntryUnitsFromString(entry.units),
+                         entry.value_uint64);
+          break;
+        case RawMemoryGraphNode::MemoryNodeEntry::EntryType::kString:
+          node->AddEntry(entry.name, entry.value_string);
+          break;
+      }
+    }
+  }
+}
+
+// static
+void GraphProcessor::AddEdges(const RawProcessMemoryNode& source,
+                              GlobalNodeGraph* global_graph) {
+  const auto& nodes_by_id = global_graph->nodes_by_id();
+  for (const auto& id_to_edge : source.allocator_nodes_edges()) {
+    auto& edge = id_to_edge.second;
+
+    // Find the source and target nodes in the global map by id.
+    auto source_it = nodes_by_id.find(edge->source);
+    auto target_it = nodes_by_id.find(edge->target);
+
+    if (source_it == nodes_by_id.end()) {
+      // If the source is missing then simply pretend the edge never existed
+      // leading to the memory being allocated to the target (if it exists).
+      continue;
+    } else if (target_it == nodes_by_id.end()) {
+      // If the target is lost but the source is present, then also ignore
+      // this edge for now.
+      // TODO(lalitm): see crbug.com/770712 for the permanent fix for this
+      // issue.
+      continue;
+    } else {
+      // Add an edge indicating the source node owns the memory of the
+      // target node with the given importance of the edge.
+      global_graph->AddNodeOwnershipEdge(source_it->second, target_it->second,
+                                         edge->importance);
+    }
+  }
+}
+
+// static
+void GraphProcessor::MarkImplicitWeakParentsRecursively(Node* node) {
+  // Ensure that we aren't in a bad state where we have an implicit node
+  // which doesn't have any children (which is not the root node).
+  PERFETTO_DCHECK(node->is_explicit() || !node->children()->empty() ||
+                  !node->parent());
+
+  // Check that at this stage, any node which is weak is only so because
+  // it was explicitly created as such.
+  PERFETTO_DCHECK(!node->is_weak() || node->is_explicit());
+
+  // If a node is already weak then all children will be marked weak at a
+  // later stage.
+  if (node->is_weak())
+    return;
+
+  // Recurse into each child and find out if all the children of this node are
+  // weak.
+  bool all_children_weak = true;
+  for (const auto& path_to_child : *node->children()) {
+    MarkImplicitWeakParentsRecursively(path_to_child.second);
+    all_children_weak = all_children_weak && path_to_child.second->is_weak();
+  }
+
+  // If all the children are weak and the parent is only an implicit one then we
+  // consider the parent as weak as well and we will later remove it.
+  node->set_weak(!node->is_explicit() && all_children_weak);
+}
+
+// static
+void GraphProcessor::MarkWeakOwnersAndChildrenRecursively(
+    Node* node,
+    std::set<const Node*>* visited) {
+  // If we've already visited this node then nothing to do.
+  if (visited->count(node) != 0)
+    return;
+
+  // If we haven't visited the node which this node owns then wait for that.
+  if (node->owns_edge() && visited->count(node->owns_edge()->target()) == 0)
+    return;
+
+  // If we haven't visited the node's parent then wait for that.
+  if (node->parent() && visited->count(node->parent()) == 0)
+    return;
+
+  // If either the node we own or our parent is weak, then mark this node
+  // as weak.
+  if ((node->owns_edge() && node->owns_edge()->target()->is_weak()) ||
+      (node->parent() && node->parent()->is_weak())) {
+    node->set_weak(true);
+  }
+  visited->insert(node);
+
+  // Recurse into each owner node to mark any other nodes.
+  for (auto* owned_by_edge : *node->owned_by_edges()) {
+    MarkWeakOwnersAndChildrenRecursively(owned_by_edge->source(), visited);
+  }
+
+  // Recurse into each child and find out if all the children of this node are
+  // weak.
+  for (const auto& path_to_child : *node->children()) {
+    MarkWeakOwnersAndChildrenRecursively(path_to_child.second, visited);
+  }
+}
+
+// static
+void GraphProcessor::RemoveWeakNodesRecursively(Node* node) {
+  auto* children = node->children();
+  for (auto child_it = children->begin(); child_it != children->end();) {
+    Node* child = child_it->second;
+
+    // If the node is weak, remove it. This automatically makes all
+    // descendents unreachable from the parents. If this node owned
+    // by another, it will have been marked earlier in
+    // |MarkWeakOwnersAndChildrenRecursively| and so will be removed
+    // by this method at some point.
+    if (child->is_weak()) {
+      child_it = children->erase(child_it);
+      continue;
+    }
+
+    // We should never be in a situation where we're about to
+    // keep a node which owns a weak node (which will be/has been
+    // removed).
+    PERFETTO_DCHECK(!child->owns_edge() ||
+                    !child->owns_edge()->target()->is_weak());
+
+    // Descend and remove all weak child nodes.
+    RemoveWeakNodesRecursively(child);
+
+    // Remove all edges with owner nodes which are weak.
+    std::vector<Edge*>* owned_by_edges = child->owned_by_edges();
+    auto new_end =
+        std::remove_if(owned_by_edges->begin(), owned_by_edges->end(),
+                       [](Edge* edge) { return edge->source()->is_weak(); });
+    owned_by_edges->erase(new_end, owned_by_edges->end());
+
+    ++child_it;
+  }
+}
+
+// static
+void GraphProcessor::AssignTracingOverhead(const std::string& allocator,
+                                           GlobalNodeGraph* global_graph,
+                                           Process* process) {
+  // This method should only be called if the allocator node exists.
+  PERFETTO_DCHECK(process->FindNode(allocator));
+
+  // Check that the tracing node exists and isn't already owning another node.
+  Node* tracing_node = process->FindNode("tracing");
+  if (!tracing_node)
+    return;
+
+  // This should be first edge associated with the tracing node.
+  PERFETTO_DCHECK(!tracing_node->owns_edge());
+
+  // Create the node under the allocator to which tracing overhead can be
+  // assigned.
+  std::string child_name = allocator + "/allocated_objects/tracing_overhead";
+  Node* child_node = process->CreateNode(MemoryAllocatorNodeId(), child_name,
+                                         false /* weak */);
+
+  // Assign the overhead of tracing to the tracing node.
+  global_graph->AddNodeOwnershipEdge(tracing_node, child_node,
+                                     0 /* importance */);
+}
+
+// static
+Node::Entry GraphProcessor::AggregateNumericWithNameForNode(
+    Node* node,
+    const std::string& name) {
+  bool first = true;
+  Node::Entry::ScalarUnits units = Node::Entry::ScalarUnits::kObjects;
+  uint64_t aggregated = 0;
+  for (auto& path_to_child : *node->children()) {
+    auto* entries = path_to_child.second->entries();
+
+    // Retrieve the entry with the given column name.
+    auto name_to_entry_it = entries->find(name);
+    if (name_to_entry_it == entries->end())
+      continue;
+
+    // Extract the entry from the iterator.
+    const Node::Entry& entry = name_to_entry_it->second;
+
+    // Ensure that the entry is numeric.
+    PERFETTO_DCHECK(entry.type == Node::Entry::Type::kUInt64);
+
+    // Check that the units of every child's entry with the given name is the
+    // same (i.e. we don't get a number for one child and size for another
+    // child). We do this by having a DCHECK that the units match the first
+    // child's units.
+    PERFETTO_DCHECK(first || units == entry.units);
+    units = entry.units;
+    aggregated += entry.value_uint64;
+    first = false;
+  }
+  return Node::Entry(units, aggregated);
+}
+
+// static
+void GraphProcessor::AggregateNumericsRecursively(Node* node) {
+  std::set<std::string> numeric_names;
+
+  for (const auto& path_to_child : *node->children()) {
+    AggregateNumericsRecursively(path_to_child.second);
+    for (const auto& name_to_entry : *path_to_child.second->entries()) {
+      const std::string& name = name_to_entry.first;
+      if (name_to_entry.second.type == Node::Entry::Type::kUInt64 &&
+          name != kSizeEntryName && name != kEffectiveSizeEntryName) {
+        numeric_names.insert(name);
+      }
+    }
+  }
+
+  for (auto& name : numeric_names) {
+    node->entries()->emplace(name, AggregateNumericWithNameForNode(node, name));
+  }
+}
+
+// static
+void GraphProcessor::PropagateNumericsAndDiagnosticsRecursively(Node* node) {
+  for (const auto& name_to_entry : *node->entries()) {
+    for (auto* edge : *node->owned_by_edges()) {
+      edge->source()->entries()->insert(name_to_entry);
+    }
+  }
+  for (const auto& path_to_child : *node->children()) {
+    PropagateNumericsAndDiagnosticsRecursively(path_to_child.second);
+  }
+}
+
+// static
+base::Optional<uint64_t> GraphProcessor::AggregateSizeForDescendantNode(
+    Node* root,
+    Node* descendant) {
+  Edge* owns_edge = descendant->owns_edge();
+  if (owns_edge && owns_edge->target()->IsDescendentOf(*root))
+    return base::make_optional(0UL);
+
+  if (descendant->children()->empty())
+    return GetSizeEntryOfNode(descendant).value_or(0ul);
+
+  base::Optional<uint64_t> size;
+  for (auto path_to_child : *descendant->children()) {
+    auto c_size = AggregateSizeForDescendantNode(root, path_to_child.second);
+    if (size) {
+      *size += c_size.value_or(0);
+    } else {
+      size = std::move(c_size);
+    }
+  }
+  return size;
+}
+
+// Assumes that this function has been called on all children and owner nodes.
+// static
+void GraphProcessor::CalculateSizeForNode(Node* node) {
+  // Get the size at the root node if it exists.
+  base::Optional<uint64_t> node_size = GetSizeEntryOfNode(node);
+
+  // Aggregate the size of all the child nodes.
+  base::Optional<uint64_t> aggregated_size;
+  for (auto path_to_child : *node->children()) {
+    auto c_size = AggregateSizeForDescendantNode(node, path_to_child.second);
+    if (aggregated_size) {
+      *aggregated_size += c_size.value_or(0ul);
+    } else {
+      aggregated_size = std::move(c_size);
+    }
+  }
+
+  // Check that if both aggregated and node sizes exist that the node size
+  // is bigger than the aggregated.
+  // TODO(lalitm): the following condition is triggered very often even though
+  // it is a warning in JS code. Find a way to add the warning to display in UI
+  // or to fix all instances where this is violated and then enable this check.
+  // PERFETTO_DCHECK(!node_size || !aggregated_size || *node_size >=
+  // *aggregated_size);
+
+  // Calculate the maximal size of an owner node.
+  base::Optional<uint64_t> max_owner_size;
+  for (auto* edge : *node->owned_by_edges()) {
+    auto o_size = GetSizeEntryOfNode(edge->source());
+    if (max_owner_size) {
+      *max_owner_size = std::max(o_size.value_or(0ul), *max_owner_size);
+    } else {
+      max_owner_size = std::move(o_size);
+    }
+  }
+
+  // Check that if both owner and node sizes exist that the node size
+  // is bigger than the owner.
+  // TODO(lalitm): the following condition is triggered very often even though
+  // it is a warning in JS code. Find a way to add the warning to display in UI
+  // or to fix all instances where this is violated and then enable this check.
+  // PERFETTO_DCHECK(!node_size || !max_owner_size || *node_size >=
+  // *max_owner_size);
+
+  // Clear out any existing size entry which may exist.
+  node->entries()->erase(kSizeEntryName);
+
+  // If no inference about size can be made then simply return.
+  if (!node_size && !aggregated_size && !max_owner_size)
+    return;
+
+  // Update the node with the new size entry.
+  uint64_t aggregated_size_value = aggregated_size.value_or(0ul);
+  uint64_t process_size =
+      std::max({node_size.value_or(0ul), aggregated_size_value,
+                max_owner_size.value_or(0ul)});
+  node->AddEntry(kSizeEntryName, Node::Entry::ScalarUnits::kBytes,
+                 process_size);
+
+  // If this is an intermediate node then add a ghost node which stores
+  // all sizes not accounted for by the children.
+  uint64_t unaccounted = process_size - aggregated_size_value;
+  if (unaccounted > 0 && !node->children()->empty()) {
+    Node* unspecified = node->CreateChild("<unspecified>");
+    unspecified->AddEntry(kSizeEntryName, Node::Entry::ScalarUnits::kBytes,
+                          unaccounted);
+  }
+}
+
+// Assumes that this function has been called on all children and owner nodes.
+// static
+void GraphProcessor::CalculateNodeSubSizes(Node* node) {
+  // Completely skip nodes with undefined size.
+  base::Optional<uint64_t> size_opt = GetSizeEntryOfNode(node);
+  if (!size_opt)
+    return;
+
+  // If the node is a leaf node, then both sub-sizes are equal to the size.
+  if (node->children()->empty()) {
+    node->add_not_owning_sub_size(*size_opt);
+    node->add_not_owned_sub_size(*size_opt);
+    return;
+  }
+
+  // Calculate this node's not-owning sub-size by summing up the not-owning
+  // sub-sizes of children which do not own another node.
+  for (const auto& path_to_child : *node->children()) {
+    if (path_to_child.second->owns_edge())
+      continue;
+    node->add_not_owning_sub_size(path_to_child.second->not_owning_sub_size());
+  }
+
+  // Calculate this node's not-owned sub-size.
+  for (const auto& path_to_child : *node->children()) {
+    Node* child = path_to_child.second;
+
+    // If the child node is not owned, then add its not-owned sub-size.
+    if (child->owned_by_edges()->empty()) {
+      node->add_not_owned_sub_size(child->not_owned_sub_size());
+      continue;
+    }
+
+    // If the child node is owned, then add the difference between its size
+    // and the largest owner.
+    uint64_t largest_owner_size = 0;
+    for (Edge* edge : *child->owned_by_edges()) {
+      uint64_t source_size = GetSizeEntryOfNode(edge->source()).value_or(0);
+      largest_owner_size = std::max(largest_owner_size, source_size);
+    }
+    uint64_t child_size = GetSizeEntryOfNode(child).value_or(0);
+    node->add_not_owned_sub_size(child_size - largest_owner_size);
+  }
+}
+
+// static
+void GraphProcessor::CalculateNodeOwnershipCoefficient(Node* node) {
+  // Completely skip nodes with undefined size.
+  base::Optional<uint64_t> size_opt = GetSizeEntryOfNode(node);
+  if (!size_opt)
+    return;
+
+  // We only need to consider owned nodes.
+  if (node->owned_by_edges()->empty())
+    return;
+
+  // Sort the owners in decreasing order of ownership priority and
+  // increasing order of not-owning sub-size (in case of equal priority).
+  std::vector<Edge*> owners = *node->owned_by_edges();
+  std::sort(owners.begin(), owners.end(), [](Edge* a, Edge* b) {
+    if (a->priority() == b->priority()) {
+      return a->source()->not_owning_sub_size() <
+             b->source()->not_owning_sub_size();
+    }
+    return b->priority() < a->priority();
+  });
+
+  // Loop over the list of owners and distribute the owned node's not-owned
+  // sub-size among them according to their ownership priority and
+  // not-owning sub-size.
+  uint64_t already_attributed_sub_size = 0;
+  for (auto current_it = owners.begin(); current_it != owners.end();) {
+    // Find the position of the first owner with lower priority.
+    int current_priority = (*current_it)->priority();
+    auto next_it =
+        std::find_if(current_it, owners.end(), [current_priority](Edge* edge) {
+          return edge->priority() < current_priority;
+        });
+
+    // Compute the number of nodes which have the same priority as current.
+    size_t difference = static_cast<size_t>(std::distance(current_it, next_it));
+
+    // Visit the owners with the same priority in increasing order of
+    // not-owned sub-size, split the owned memory among them appropriately,
+    // and calculate their owning coefficients.
+    double attributed_not_owning_sub_size = 0;
+    for (; current_it != next_it; current_it++) {
+      uint64_t not_owning_sub_size =
+          (*current_it)->source()->not_owning_sub_size();
+      if (not_owning_sub_size > already_attributed_sub_size) {
+        attributed_not_owning_sub_size +=
+            static_cast<double>(not_owning_sub_size -
+                                already_attributed_sub_size) /
+            static_cast<double>(difference);
+        already_attributed_sub_size = not_owning_sub_size;
+      }
+
+      if (not_owning_sub_size != 0) {
+        double coeff = attributed_not_owning_sub_size /
+                       static_cast<double>(not_owning_sub_size);
+        (*current_it)->source()->set_owning_coefficient(coeff);
+      }
+      difference--;
+    }
+
+    // At the end of this loop, we should move to a node with a lower priority.
+    PERFETTO_DCHECK(current_it == next_it);
+  }
+
+  // Attribute the remainder of the owned node's not-owned sub-size to
+  // the node itself and calculate its owned coefficient.
+  uint64_t not_owned_sub_size = node->not_owned_sub_size();
+  if (not_owned_sub_size != 0) {
+    double remainder_sub_size =
+        static_cast<double>(not_owned_sub_size - already_attributed_sub_size);
+    node->set_owned_coefficient(remainder_sub_size /
+                                static_cast<double>(not_owned_sub_size));
+  }
+}
+
+// static
+void GraphProcessor::CalculateNodeCumulativeOwnershipCoefficient(Node* node) {
+  // Completely skip nodes with undefined size.
+  base::Optional<uint64_t> size_opt = GetSizeEntryOfNode(node);
+  if (!size_opt)
+    return;
+
+  double cumulative_owned_coefficient = node->owned_coefficient();
+  if (node->parent()) {
+    cumulative_owned_coefficient *=
+        node->parent()->cumulative_owned_coefficient();
+  }
+  node->set_cumulative_owned_coefficient(cumulative_owned_coefficient);
+
+  if (node->owns_edge()) {
+    node->set_cumulative_owning_coefficient(
+        node->owning_coefficient() *
+        node->owns_edge()->target()->cumulative_owning_coefficient());
+  } else if (node->parent()) {
+    node->set_cumulative_owning_coefficient(
+        node->parent()->cumulative_owning_coefficient());
+  } else {
+    node->set_cumulative_owning_coefficient(1);
+  }
+}
+
+// static
+void GraphProcessor::CalculateNodeEffectiveSize(Node* node) {
+  // Completely skip nodes with undefined size. As a result, each node will
+  // have defined effective size if and only if it has defined size.
+  base::Optional<uint64_t> size_opt = GetSizeEntryOfNode(node);
+  if (!size_opt) {
+    node->entries()->erase(kEffectiveSizeEntryName);
+    return;
+  }
+
+  uint64_t effective_size = 0;
+  if (node->children()->empty()) {
+    // Leaf node.
+    effective_size = static_cast<uint64_t>(
+        static_cast<double>(*size_opt) * node->cumulative_owning_coefficient() *
+        node->cumulative_owned_coefficient());
+  } else {
+    // Non-leaf node.
+    for (const auto& path_to_child : *node->children()) {
+      Node* child = path_to_child.second;
+      if (!GetSizeEntryOfNode(child))
+        continue;
+      effective_size +=
+          child->entries()->find(kEffectiveSizeEntryName)->second.value_uint64;
+    }
+  }
+  node->AddEntry(kEffectiveSizeEntryName, Node::Entry::ScalarUnits::kBytes,
+                 effective_size);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/graph_processor_unittest.cc b/src/trace_processor/importers/memory_tracker/graph_processor_unittest.cc
new file mode 100644
index 0000000..8d1cc31
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/graph_processor_unittest.cc
@@ -0,0 +1,684 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"
+
+#include <stddef.h>
+
+#include <unordered_map>
+
+#include "perfetto/base/build_config.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using Edge = GlobalNodeGraph::Edge;
+using Node = GlobalNodeGraph::Node;
+using Process = GlobalNodeGraph::Process;
+
+namespace {
+
+const MemoryAllocatorNodeId kEmptyId;
+
+}  // namespace
+
+class GraphProcessorTest : public testing::Test {
+ public:
+  GraphProcessorTest() {}
+
+  void MarkImplicitWeakParentsRecursively(Node* node) {
+    GraphProcessor::MarkImplicitWeakParentsRecursively(node);
+  }
+
+  void MarkWeakOwnersAndChildrenRecursively(Node* node) {
+    std::set<const Node*> visited;
+    GraphProcessor::MarkWeakOwnersAndChildrenRecursively(node, &visited);
+  }
+
+  void RemoveWeakNodesRecursively(Node* node) {
+    GraphProcessor::RemoveWeakNodesRecursively(node);
+  }
+
+  void AssignTracingOverhead(const std::string& allocator,
+                             GlobalNodeGraph* global_graph,
+                             Process* process) {
+    GraphProcessor::AssignTracingOverhead(allocator, global_graph, process);
+  }
+
+  GlobalNodeGraph::Node::Entry AggregateNumericWithNameForNode(
+      Node* node,
+      const std::string& name) {
+    return GraphProcessor::AggregateNumericWithNameForNode(node, name);
+  }
+
+  void AggregateNumericsRecursively(Node* node) {
+    GraphProcessor::AggregateNumericsRecursively(node);
+  }
+
+  void PropagateNumericsAndDiagnosticsRecursively(Node* node) {
+    GraphProcessor::PropagateNumericsAndDiagnosticsRecursively(node);
+  }
+
+  base::Optional<uint64_t> AggregateSizeForDescendantNode(Node* root,
+                                                          Node* descendant) {
+    return GraphProcessor::AggregateSizeForDescendantNode(root, descendant);
+  }
+
+  void CalculateSizeForNode(Node* node) {
+    GraphProcessor::CalculateSizeForNode(node);
+  }
+
+  void CalculateNodeSubSizes(Node* node) {
+    GraphProcessor::CalculateNodeSubSizes(node);
+  }
+
+  void CalculateNodeOwnershipCoefficient(Node* node) {
+    GraphProcessor::CalculateNodeOwnershipCoefficient(node);
+  }
+
+  void CalculateNodeCumulativeOwnershipCoefficient(Node* node) {
+    GraphProcessor::CalculateNodeCumulativeOwnershipCoefficient(node);
+  }
+
+  void CalculateNodeEffectiveSize(Node* node) {
+    GraphProcessor::CalculateNodeEffectiveSize(node);
+  }
+
+ protected:
+  GlobalNodeGraph graph;
+};
+
+TEST_F(GraphProcessorTest, SmokeComputeMemoryGraph) {
+  std::map<base::PlatformProcessId, std::unique_ptr<RawProcessMemoryNode>>
+      process_nodes;
+
+  std::unique_ptr<RawMemoryGraphNode> source(new RawMemoryGraphNode(
+      "test1/test2/test3", LevelOfDetail::kDetailed, MemoryAllocatorNodeId(42),
+      std::vector<RawMemoryGraphNode::MemoryNodeEntry>{
+          {RawMemoryGraphNode::kNameSize, RawMemoryGraphNode::kUnitsBytes,
+           10}}));
+
+  std::unique_ptr<RawMemoryGraphNode> target(new RawMemoryGraphNode(
+      "target", LevelOfDetail::kDetailed, MemoryAllocatorNodeId(4242)));
+
+  std::unique_ptr<MemoryGraphEdge> edge(
+      new MemoryGraphEdge(source->id(), target->id(), 10, false));
+  RawProcessMemoryNode::AllocatorNodeEdgesMap edgesMap;
+  edgesMap.emplace(edge->source, std::move(edge));
+
+  RawProcessMemoryNode::MemoryNodesMap nodesMap;
+  nodesMap.emplace(source->absolute_name(), std::move(source));
+  nodesMap.emplace(target->absolute_name(), std::move(target));
+
+  auto pmd = std::unique_ptr<RawProcessMemoryNode>(new RawProcessMemoryNode(
+      LevelOfDetail::kDetailed, std::move(edgesMap), std::move(nodesMap)));
+  process_nodes.emplace(1, std::move(pmd));
+
+  auto global_node = GraphProcessor::CreateMemoryGraph(process_nodes);
+
+  ASSERT_EQ(1u, global_node->process_node_graphs().size());
+
+  auto id_to_node_it = global_node->process_node_graphs().find(1);
+  auto* first_child = id_to_node_it->second->FindNode("test1");
+  ASSERT_NE(first_child, nullptr);
+  ASSERT_EQ(first_child->parent(), id_to_node_it->second->root());
+
+  auto* second_child = first_child->GetChild("test2");
+  ASSERT_NE(second_child, nullptr);
+  ASSERT_EQ(second_child->parent(), first_child);
+
+  auto* third_child = second_child->GetChild("test3");
+  ASSERT_NE(third_child, nullptr);
+  ASSERT_EQ(third_child->parent(), second_child);
+
+  auto* direct = id_to_node_it->second->FindNode("test1/test2/test3");
+  ASSERT_EQ(third_child, direct);
+
+  ASSERT_EQ(third_child->entries()->size(), 1ul);
+
+  auto size = third_child->entries()->find(RawMemoryGraphNode::kNameSize);
+  ASSERT_EQ(10ul, size->second.value_uint64);
+
+  auto& edges = global_node->edges();
+  auto edge_it = edges.begin();
+  ASSERT_EQ(std::distance(edges.begin(), edges.end()), 1l);
+  ASSERT_EQ(edge_it->source(), direct);
+  ASSERT_EQ(edge_it->target(), id_to_node_it->second->FindNode("target"));
+  ASSERT_EQ(edge_it->priority(), 10);
+}
+
+TEST_F(GraphProcessorTest, ComputeSharedFootprintFromGraphSameImportance) {
+  Process* global_process = graph.shared_memory_graph();
+  Node* global_node = global_process->CreateNode(kEmptyId, "global/1", false);
+  global_node->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 100);
+
+  Process* first = graph.CreateGraphForProcess(1);
+  Node* shared_1 = first->CreateNode(kEmptyId, "shared_memory/1", false);
+
+  Process* second = graph.CreateGraphForProcess(2);
+  Node* shared_2 = second->CreateNode(kEmptyId, "shared_memory/2", false);
+
+  graph.AddNodeOwnershipEdge(shared_1, global_node, 1);
+  graph.AddNodeOwnershipEdge(shared_2, global_node, 1);
+
+  auto pid_to_sizes = GraphProcessor::ComputeSharedFootprintFromGraph(graph);
+  ASSERT_EQ(pid_to_sizes[1], 50ul);
+  ASSERT_EQ(pid_to_sizes[2], 50ul);
+}
+
+TEST_F(GraphProcessorTest, ComputeSharedFootprintFromGraphSomeDiffImportance) {
+  Process* global_process = graph.shared_memory_graph();
+
+  Node* global_node = global_process->CreateNode(kEmptyId, "global/1", false);
+  global_node->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 100);
+
+  Process* first = graph.CreateGraphForProcess(1);
+  Node* shared_1 = first->CreateNode(kEmptyId, "shared_memory/1", false);
+
+  Process* second = graph.CreateGraphForProcess(2);
+  Node* shared_2 = second->CreateNode(kEmptyId, "shared_memory/2", false);
+
+  Process* third = graph.CreateGraphForProcess(3);
+  Node* shared_3 = third->CreateNode(kEmptyId, "shared_memory/3", false);
+
+  Process* fourth = graph.CreateGraphForProcess(4);
+  Node* shared_4 = fourth->CreateNode(kEmptyId, "shared_memory/4", false);
+
+  Process* fifth = graph.CreateGraphForProcess(5);
+  Node* shared_5 = fifth->CreateNode(kEmptyId, "shared_memory/5", false);
+
+  graph.AddNodeOwnershipEdge(shared_1, global_node, 1);
+  graph.AddNodeOwnershipEdge(shared_2, global_node, 2);
+  graph.AddNodeOwnershipEdge(shared_3, global_node, 3);
+  graph.AddNodeOwnershipEdge(shared_4, global_node, 3);
+  graph.AddNodeOwnershipEdge(shared_5, global_node, 3);
+
+  auto pid_to_sizes = GraphProcessor::ComputeSharedFootprintFromGraph(graph);
+  ASSERT_EQ(pid_to_sizes[1], 0ul);
+  ASSERT_EQ(pid_to_sizes[2], 0ul);
+  ASSERT_EQ(pid_to_sizes[3], 33ul);
+  ASSERT_EQ(pid_to_sizes[4], 33ul);
+  ASSERT_EQ(pid_to_sizes[5], 33ul);
+}
+
+TEST_F(GraphProcessorTest, MarkWeakParentsSimple) {
+  Process* process = graph.CreateGraphForProcess(1);
+  Node* parent = process->CreateNode(kEmptyId, "parent", false);
+  Node* first = process->CreateNode(kEmptyId, "parent/first", true);
+  Node* second = process->CreateNode(kEmptyId, "parent/second", false);
+
+  // Case where one child is not weak.
+  parent->set_explicit(false);
+  first->set_explicit(true);
+  second->set_explicit(true);
+
+  // The function should be a no-op.
+  MarkImplicitWeakParentsRecursively(parent);
+  ASSERT_FALSE(parent->is_weak());
+  ASSERT_TRUE(first->is_weak());
+  ASSERT_FALSE(second->is_weak());
+
+  // Case where all children is weak.
+  second->set_weak(true);
+
+  // The function should mark parent as weak.
+  MarkImplicitWeakParentsRecursively(parent);
+  ASSERT_TRUE(parent->is_weak());
+  ASSERT_TRUE(first->is_weak());
+  ASSERT_TRUE(second->is_weak());
+}
+
+TEST_F(GraphProcessorTest, MarkWeakParentsComplex) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  // |first| is explicitly strong but |first_child| is implicitly so.
+  Node* parent = process->CreateNode(kEmptyId, "parent", false);
+  Node* first = process->CreateNode(kEmptyId, "parent/f", false);
+  Node* first_child = process->CreateNode(kEmptyId, "parent/f/c", false);
+  Node* first_gchild = process->CreateNode(kEmptyId, "parent/f/c/c", true);
+
+  parent->set_explicit(false);
+  first->set_explicit(true);
+  first_child->set_explicit(false);
+  first_gchild->set_explicit(true);
+
+  // That should lead to |first_child| marked implicitly weak.
+  MarkImplicitWeakParentsRecursively(parent);
+  ASSERT_FALSE(parent->is_weak());
+  ASSERT_FALSE(first->is_weak());
+  ASSERT_TRUE(first_child->is_weak());
+  ASSERT_TRUE(first_gchild->is_weak());
+
+  // Reset and change so that first is now only implicitly strong.
+  first->set_explicit(false);
+  first_child->set_weak(false);
+
+  // The whole chain should now be weak.
+  MarkImplicitWeakParentsRecursively(parent);
+  ASSERT_TRUE(parent->is_weak());
+  ASSERT_TRUE(first->is_weak());
+  ASSERT_TRUE(first_child->is_weak());
+  ASSERT_TRUE(first_gchild->is_weak());
+}
+
+TEST_F(GraphProcessorTest, MarkWeakOwners) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  // Make only the ultimate owned node weak.
+  Node* owner = process->CreateNode(kEmptyId, "owner", false);
+  Node* owned = process->CreateNode(kEmptyId, "owned", false);
+  Node* owned_2 = process->CreateNode(kEmptyId, "owned2", true);
+
+  graph.AddNodeOwnershipEdge(owner, owned, 0);
+  graph.AddNodeOwnershipEdge(owned, owned_2, 0);
+
+  // Starting from leaf node should lead to everything being weak.
+  MarkWeakOwnersAndChildrenRecursively(process->root());
+  ASSERT_TRUE(owner->is_weak());
+  ASSERT_TRUE(owned->is_weak());
+  ASSERT_TRUE(owned_2->is_weak());
+}
+
+TEST_F(GraphProcessorTest, MarkWeakParent) {
+  Process* process = graph.CreateGraphForProcess(1);
+  Node* parent = process->CreateNode(kEmptyId, "parent", true);
+  Node* child = process->CreateNode(kEmptyId, "parent/c", false);
+  Node* child_2 = process->CreateNode(kEmptyId, "parent/c/c", false);
+
+  // Starting from parent node should lead to everything being weak.
+  MarkWeakOwnersAndChildrenRecursively(process->root());
+  ASSERT_TRUE(parent->is_weak());
+  ASSERT_TRUE(child->is_weak());
+  ASSERT_TRUE(child_2->is_weak());
+}
+
+TEST_F(GraphProcessorTest, MarkWeakParentOwner) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  // Make only the parent node weak.
+  Node* parent = process->CreateNode(kEmptyId, "parent", true);
+  Node* child = process->CreateNode(kEmptyId, "parent/c", false);
+  Node* child_2 = process->CreateNode(kEmptyId, "parent/c/c", false);
+  Node* owner = process->CreateNode(kEmptyId, "owner", false);
+
+  graph.AddNodeOwnershipEdge(owner, parent, 0);
+
+  // Starting from parent node should lead to everything being weak.
+  MarkWeakOwnersAndChildrenRecursively(process->root());
+  ASSERT_TRUE(parent->is_weak());
+  ASSERT_TRUE(child->is_weak());
+  ASSERT_TRUE(child_2->is_weak());
+  ASSERT_TRUE(owner->is_weak());
+}
+
+TEST_F(GraphProcessorTest, RemoveWeakNodesRecursively) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  // Make only the child node weak.
+  Node* parent = process->CreateNode(kEmptyId, "parent", false);
+  Node* child = process->CreateNode(kEmptyId, "parent/c", true);
+  process->CreateNode(kEmptyId, "parent/c/c", false);
+  Node* owned = process->CreateNode(kEmptyId, "parent/owned", false);
+
+  graph.AddNodeOwnershipEdge(child, owned, 0);
+
+  // Starting from parent node should lead child and child_2 being
+  // removed and owned to have the edge from it removed.
+  RemoveWeakNodesRecursively(parent);
+
+  ASSERT_EQ(parent->children()->size(), 1ul);
+  ASSERT_EQ(parent->children()->begin()->second, owned);
+
+  ASSERT_TRUE(owned->owned_by_edges()->empty());
+}
+
+TEST_F(GraphProcessorTest, RemoveWeakNodesRecursivelyBetweenGraphs) {
+  Process* f_process = graph.CreateGraphForProcess(1);
+  Process* s_process = graph.CreateGraphForProcess(2);
+
+  // Make only the child node weak.
+  Node* child = f_process->CreateNode(kEmptyId, "c", true);
+  f_process->CreateNode(kEmptyId, "c/c", false);
+  Node* owned = s_process->CreateNode(kEmptyId, "owned", false);
+
+  graph.AddNodeOwnershipEdge(child, owned, 0);
+
+  // Starting from root node should lead child and child_2 being
+  // removed.
+  RemoveWeakNodesRecursively(f_process->root());
+
+  ASSERT_EQ(f_process->root()->children()->size(), 0ul);
+  ASSERT_EQ(s_process->root()->children()->size(), 1ul);
+
+  // This should be false until our next pass.
+  ASSERT_FALSE(owned->owned_by_edges()->empty());
+
+  RemoveWeakNodesRecursively(s_process->root());
+
+  // We should now have cleaned up the owned node's edges.
+  ASSERT_TRUE(owned->owned_by_edges()->empty());
+}
+
+TEST_F(GraphProcessorTest, AssignTracingOverhead) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  // Now add an allocator node.
+  process->CreateNode(kEmptyId, "malloc", false);
+
+  // If the tracing node does not exist, this should do nothing.
+  AssignTracingOverhead("malloc", &graph, process);
+  ASSERT_TRUE(process->root()->GetChild("malloc")->children()->empty());
+
+  // Now add a tracing node.
+  process->CreateNode(kEmptyId, "tracing", false);
+
+  // This should now add a node with the allocator.
+  AssignTracingOverhead("malloc", &graph, process);
+  ASSERT_NE(process->FindNode("malloc/allocated_objects/tracing_overhead"),
+            nullptr);
+}
+
+TEST_F(GraphProcessorTest, AggregateNumericWithNameForNode) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process->CreateNode(kEmptyId, "c2", false);
+  Node* c3 = process->CreateNode(kEmptyId, "c3", false);
+
+  c1->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 100);
+  c2->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 256);
+  c3->AddEntry("other_numeric", Node::Entry::ScalarUnits::kBytes, 1000);
+
+  Node* root = process->root();
+  Node::Entry entry = AggregateNumericWithNameForNode(root, "random_numeric");
+  ASSERT_EQ(entry.value_uint64, 356ul);
+  ASSERT_EQ(entry.units, Node::Entry::ScalarUnits::kBytes);
+}
+
+TEST_F(GraphProcessorTest, AggregateNumericsRecursively) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process->CreateNode(kEmptyId, "c2", false);
+  Node* c2_c1 = process->CreateNode(kEmptyId, "c2/c1", false);
+  Node* c2_c2 = process->CreateNode(kEmptyId, "c2/c2", false);
+  Node* c3_c1 = process->CreateNode(kEmptyId, "c3/c1", false);
+  Node* c3_c2 = process->CreateNode(kEmptyId, "c3/c2", false);
+
+  // If an entry already exists in the parent, the child should not
+  // ovewrite it. If nothing exists, then the child can aggregrate.
+  c1->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 100);
+  c2->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 256);
+  c2_c1->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 256);
+  c2_c2->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 256);
+  c3_c1->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 10);
+  c3_c2->AddEntry("random_numeric", Node::Entry::ScalarUnits::kBytes, 10);
+
+  Node* root = process->root();
+  AggregateNumericsRecursively(root);
+  ASSERT_EQ(root->entries()->size(), 1ul);
+
+  auto entry = root->entries()->begin()->second;
+  ASSERT_EQ(entry.value_uint64, 376ul);
+  ASSERT_EQ(entry.units, Node::Entry::ScalarUnits::kBytes);
+}
+
+TEST_F(GraphProcessorTest, AggregateSizeForDescendantNode) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process->CreateNode(kEmptyId, "c2", false);
+  Node* c2_c1 = process->CreateNode(kEmptyId, "c2/c1", false);
+  Node* c2_c2 = process->CreateNode(kEmptyId, "c2/c2", false);
+  Node* c3_c1 = process->CreateNode(kEmptyId, "c3/c1", false);
+  Node* c3_c2 = process->CreateNode(kEmptyId, "c3/c2", false);
+
+  c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 100);
+  c2_c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 256);
+  c2_c2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 256);
+  c3_c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 10);
+  c3_c2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 10);
+
+  graph.AddNodeOwnershipEdge(c2_c2, c3_c2, 0);
+
+  // Aggregating root should give size of (100 + 256 + 10 * 2) = 376.
+  // |c2_c2| is not counted because it is owns by |c3_c2|.
+  Node* root = process->root();
+  ASSERT_EQ(376ul, *AggregateSizeForDescendantNode(root, root));
+
+  // Aggregating c2 should give size of (256 * 2) = 512. |c2_c2| is counted
+  // because |c3_c2| is not a child of |c2|.
+  ASSERT_EQ(512ul, *AggregateSizeForDescendantNode(c2, c2));
+}
+
+TEST_F(GraphProcessorTest, CalculateSizeForNode) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process->CreateNode(kEmptyId, "c2", false);
+  Node* c2_c1 = process->CreateNode(kEmptyId, "c2/c1", false);
+  Node* c2_c2 = process->CreateNode(kEmptyId, "c2/c2", false);
+  Node* c3 = process->CreateNode(kEmptyId, "c3", false);
+  Node* c3_c1 = process->CreateNode(kEmptyId, "c3/c1", false);
+  Node* c3_c2 = process->CreateNode(kEmptyId, "c3/c2", false);
+
+  c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 600);
+  c2_c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 10);
+  c2_c2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 10);
+  c3->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 600);
+  c3_c1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 256);
+  c3_c2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 256);
+
+  graph.AddNodeOwnershipEdge(c2_c2, c3_c2, 0);
+
+  // Compute size entry for |c2| since computations for |c2_c1| and |c2_c2|
+  // are already complete.
+  CalculateSizeForNode(c2);
+
+  // Check that |c2| now has a size entry of 20 (sum of children).
+  auto c2_entry = c2->entries()->begin()->second;
+  ASSERT_EQ(c2_entry.value_uint64, 20ul);
+  ASSERT_EQ(c2_entry.units, Node::Entry::ScalarUnits::kBytes);
+
+  // Compute size entry for |c3_c2| which should not change in size.
+  CalculateSizeForNode(c3_c2);
+
+  // Check that |c3_c2| now has unchanged size.
+  auto c3_c2_entry = c3_c2->entries()->begin()->second;
+  ASSERT_EQ(c3_c2_entry.value_uint64, 256ul);
+  ASSERT_EQ(c3_c2_entry.units, Node::Entry::ScalarUnits::kBytes);
+
+  // Compute size entry for |c3| which should add an unspecified node.
+  CalculateSizeForNode(c3);
+
+  // Check that |c3| has unchanged size.
+  auto c3_entry = c3->entries()->begin()->second;
+  ASSERT_EQ(c3_entry.value_uint64, 600ul);
+  ASSERT_EQ(c3_entry.units, Node::Entry::ScalarUnits::kBytes);
+
+  // Check that the unspecified node is a child of |c3| and has size
+  // 600 - 512 = 88.
+  Node* c3_child = c3->children()->find("<unspecified>")->second;
+  auto c3_child_entry = c3_child->entries()->begin()->second;
+  ASSERT_EQ(c3_child_entry.value_uint64, 88ul);
+  ASSERT_EQ(c3_child_entry.units, Node::Entry::ScalarUnits::kBytes);
+
+  // Compute size entry for |root| which should aggregate children sizes.
+  CalculateSizeForNode(process->root());
+
+  // Check that |root| has been assigned a size of 600 + 10 + 600 = 1210.
+  // Note that |c2_c2| is not counted because it ows |c3_c2| which is a
+  // descendant of |root|.
+  auto root_entry = process->root()->entries()->begin()->second;
+  ASSERT_EQ(root_entry.value_uint64, 1210ul);
+  ASSERT_EQ(root_entry.units, Node::Entry::ScalarUnits::kBytes);
+}
+
+TEST_F(GraphProcessorTest, CalculateNodeSubSizes) {
+  Process* process_1 = graph.CreateGraphForProcess(1);
+  Process* process_2 = graph.CreateGraphForProcess(2);
+
+  Node* parent_1 = process_1->CreateNode(kEmptyId, "parent", false);
+  Node* child_1 = process_1->CreateNode(kEmptyId, "parent/child", false);
+
+  Node* parent_2 = process_2->CreateNode(kEmptyId, "parent", false);
+  Node* child_2 = process_2->CreateNode(kEmptyId, "parent/child", false);
+
+  graph.AddNodeOwnershipEdge(parent_1, parent_2, 0);
+
+  process_1->root()->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 4);
+  parent_1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 4);
+  child_1->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 4);
+  process_2->root()->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 5);
+  parent_2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 5);
+  child_2->AddEntry("size", Node::Entry::ScalarUnits::kBytes, 5);
+
+  // Each of these nodes should have owner/owned same as size itself.
+  CalculateNodeSubSizes(child_1);
+  ASSERT_EQ(child_1->not_owned_sub_size(), 4ul);
+  ASSERT_EQ(child_1->not_owning_sub_size(), 4ul);
+  CalculateNodeSubSizes(child_2);
+  ASSERT_EQ(child_2->not_owned_sub_size(), 5ul);
+  ASSERT_EQ(child_2->not_owning_sub_size(), 5ul);
+
+  // These nodes should also have size of children.
+  CalculateNodeSubSizes(parent_1);
+  ASSERT_EQ(parent_1->not_owned_sub_size(), 4ul);
+  ASSERT_EQ(parent_1->not_owning_sub_size(), 4ul);
+  CalculateNodeSubSizes(parent_2);
+  ASSERT_EQ(parent_2->not_owned_sub_size(), 5ul);
+  ASSERT_EQ(parent_2->not_owning_sub_size(), 5ul);
+
+  // These nodes should account for edge between parents.
+  CalculateNodeSubSizes(process_1->root());
+  ASSERT_EQ(process_1->root()->not_owned_sub_size(), 4ul);
+  ASSERT_EQ(process_1->root()->not_owning_sub_size(), 0ul);
+  CalculateNodeSubSizes(process_2->root());
+  ASSERT_EQ(process_2->root()->not_owned_sub_size(), 1ul);
+  ASSERT_EQ(process_2->root()->not_owning_sub_size(), 5ul);
+}
+
+TEST_F(GraphProcessorTest, CalculateNodeOwnershipCoefficient) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* owned = process->CreateNode(kEmptyId, "owned", false);
+  Node* owner_1 = process->CreateNode(kEmptyId, "owner1", false);
+  Node* owner_2 = process->CreateNode(kEmptyId, "owner2", false);
+  Node* owner_3 = process->CreateNode(kEmptyId, "owner3", false);
+  Node* owner_4 = process->CreateNode(kEmptyId, "owner4", false);
+
+  graph.AddNodeOwnershipEdge(owner_1, owned, 2);
+  graph.AddNodeOwnershipEdge(owner_2, owned, 2);
+  graph.AddNodeOwnershipEdge(owner_3, owned, 1);
+  graph.AddNodeOwnershipEdge(owner_4, owned, 0);
+
+  // Ensure the owned node has a size otherwise calculations will not happen.
+  owned->AddEntry("size", Node::Entry::kBytes, 10);
+
+  // Setup the owned/owning sub sizes.
+  owned->add_not_owned_sub_size(10);
+  owner_1->add_not_owning_sub_size(6);
+  owner_2->add_not_owning_sub_size(7);
+  owner_3->add_not_owning_sub_size(5);
+  owner_4->add_not_owning_sub_size(8);
+
+  // Perform the computation.
+  CalculateNodeOwnershipCoefficient(owned);
+
+  // Ensure that the coefficients are correct.
+  ASSERT_DOUBLE_EQ(owned->owned_coefficient(), 2.0 / 10.0);
+  ASSERT_DOUBLE_EQ(owner_1->owning_coefficient(), 3.0 / 6.0);
+  ASSERT_DOUBLE_EQ(owner_2->owning_coefficient(), 4.0 / 7.0);
+  ASSERT_DOUBLE_EQ(owner_3->owning_coefficient(), 0.0 / 5.0);
+  ASSERT_DOUBLE_EQ(owner_4->owning_coefficient(), 1.0 / 8.0);
+}
+
+TEST_F(GraphProcessorTest, CalculateNodeCumulativeOwnershipCoefficient) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c1_c1 = process->CreateNode(kEmptyId, "c1/c1", false);
+  Node* c1_c2 = process->CreateNode(kEmptyId, "c1/c2", false);
+  Node* owned = process->CreateNode(kEmptyId, "owned", false);
+
+  graph.AddNodeOwnershipEdge(c1_c2, owned, 2);
+
+  // Ensure all nodes have sizes otherwise calculations will not happen.
+  c1_c1->AddEntry("size", Node::Entry::kBytes, 10);
+  c1_c2->AddEntry("size", Node::Entry::kBytes, 10);
+  owned->AddEntry("size", Node::Entry::kBytes, 10);
+
+  // Setup the owned/owning cummulative coefficients.
+  c1->set_cumulative_owning_coefficient(0.123);
+  c1->set_cumulative_owned_coefficient(0.456);
+  owned->set_cumulative_owning_coefficient(0.789);
+  owned->set_cumulative_owned_coefficient(0.987);
+
+  // Set owning and owned for the children.
+  c1_c1->set_owning_coefficient(0.654);
+  c1_c1->set_owned_coefficient(0.321);
+  c1_c2->set_owning_coefficient(0.135);
+  c1_c2->set_owned_coefficient(0.246);
+
+  // Perform the computation and check our answers.
+  CalculateNodeCumulativeOwnershipCoefficient(c1_c1);
+  ASSERT_DOUBLE_EQ(c1_c1->cumulative_owning_coefficient(), 0.123);
+  ASSERT_DOUBLE_EQ(c1_c1->cumulative_owned_coefficient(), 0.456 * 0.321);
+
+  CalculateNodeCumulativeOwnershipCoefficient(c1_c2);
+  ASSERT_DOUBLE_EQ(c1_c2->cumulative_owning_coefficient(), 0.135 * 0.789);
+  ASSERT_DOUBLE_EQ(c1_c2->cumulative_owned_coefficient(), 0.456 * 0.246);
+}
+
+TEST_F(GraphProcessorTest, CalculateNodeEffectiveSize) {
+  Process* process = graph.CreateGraphForProcess(1);
+
+  Node* c1 = process->CreateNode(kEmptyId, "c1", false);
+  Node* c1_c1 = process->CreateNode(kEmptyId, "c1/c1", false);
+  Node* c1_c2 = process->CreateNode(kEmptyId, "c1/c2", false);
+
+  // Ensure all nodes have sizes otherwise calculations will not happen.
+  c1->AddEntry("size", Node::Entry::kBytes, 200);
+  c1_c1->AddEntry("size", Node::Entry::kBytes, 32);
+  c1_c2->AddEntry("size", Node::Entry::kBytes, 20);
+
+  // Setup the owned/owning cummulative coefficients.
+  c1_c1->set_cumulative_owning_coefficient(0.123);
+  c1_c1->set_cumulative_owned_coefficient(0.456);
+  c1_c2->set_cumulative_owning_coefficient(0.789);
+  c1_c2->set_cumulative_owned_coefficient(0.987);
+
+  // Perform the computation and check our answers.
+  CalculateNodeEffectiveSize(c1_c1);
+  const Node::Entry& entry_c1_c1 =
+      c1_c1->entries()->find("effective_size")->second;
+  uint64_t expected_c1_c1 = static_cast<int>(0.123 * 0.456 * 32);
+  ASSERT_EQ(entry_c1_c1.value_uint64, expected_c1_c1);
+
+  CalculateNodeEffectiveSize(c1_c2);
+  const Node::Entry& entry_c1_c2 =
+      c1_c2->entries()->find("effective_size")->second;
+  uint64_t expected_c1_c2 = static_cast<int>(0.789 * 0.987 * 20);
+  ASSERT_EQ(entry_c1_c2.value_uint64, expected_c1_c2);
+
+  CalculateNodeEffectiveSize(c1);
+  const Node::Entry& entry_c1 = c1->entries()->find("effective_size")->second;
+  ASSERT_EQ(entry_c1.value_uint64, expected_c1_c1 + expected_c1_c2);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/graph_unittest.cc b/src/trace_processor/importers/memory_tracker/graph_unittest.cc
new file mode 100644
index 0000000..6017c73
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/graph_unittest.cc
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph.h"
+
+#include "perfetto/base/build_config.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+using Node = GlobalNodeGraph::Node;
+using Process = GlobalNodeGraph::Process;
+
+const MemoryAllocatorNodeId kEmptyId;
+
+}  // namespace
+
+TEST(GlobalNodeGraphTest, CreateContainerForProcess) {
+  GlobalNodeGraph global_dump_graph;
+
+  Process* dump = global_dump_graph.CreateGraphForProcess(10);
+  ASSERT_NE(dump, nullptr);
+
+  auto* map = global_dump_graph.process_node_graphs().find(10)->second.get();
+  ASSERT_EQ(dump, map);
+}
+
+TEST(GlobalNodeGraphTest, AddNodeOwnershipEdge) {
+  GlobalNodeGraph global_dump_graph;
+  Node owner(global_dump_graph.shared_memory_graph(), nullptr);
+  Node owned(global_dump_graph.shared_memory_graph(), nullptr);
+
+  global_dump_graph.AddNodeOwnershipEdge(&owner, &owned, 1);
+
+  auto& edges = global_dump_graph.edges();
+  ASSERT_NE(edges.begin(), edges.end());
+
+  auto& edge = *edges.begin();
+  ASSERT_EQ(edge.source(), &owner);
+  ASSERT_EQ(edge.target(), &owned);
+  ASSERT_EQ(edge.priority(), 1);
+}
+
+TEST(GlobalNodeGraphTest, VisitInDepthFirstPostOrder) {
+  GlobalNodeGraph graph;
+  Process* process_1 = graph.CreateGraphForProcess(1);
+  Process* process_2 = graph.CreateGraphForProcess(2);
+
+  Node* c1 = process_1->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process_1->CreateNode(kEmptyId, "c2", false);
+  Node* c2_c1 = process_1->CreateNode(kEmptyId, "c2/c1", false);
+  Node* c2_c2 = process_1->CreateNode(kEmptyId, "c2/c2", false);
+
+  Node* c3 = process_2->CreateNode(kEmptyId, "c3", false);
+  Node* c3_c1 = process_2->CreateNode(kEmptyId, "c3/c1", false);
+  Node* c3_c2 = process_2->CreateNode(kEmptyId, "c3/c2", false);
+
+  // |c3_c2| owns |c2_c2|.
+  graph.AddNodeOwnershipEdge(c3_c2, c2_c2, 1);
+
+  // This method should always call owners and then children before the node
+  // itself.
+  auto iterator = graph.VisitInDepthFirstPostOrder();
+  ASSERT_EQ(iterator.next(), graph.shared_memory_graph()->root());
+  ASSERT_EQ(iterator.next(), c1);
+  ASSERT_EQ(iterator.next(), c2_c1);
+  ASSERT_EQ(iterator.next(), c3_c2);
+  ASSERT_EQ(iterator.next(), c2_c2);
+  ASSERT_EQ(iterator.next(), c2);
+  ASSERT_EQ(iterator.next(), process_1->root());
+  ASSERT_EQ(iterator.next(), c3_c1);
+  ASSERT_EQ(iterator.next(), c3);
+  ASSERT_EQ(iterator.next(), process_2->root());
+  ASSERT_EQ(iterator.next(), nullptr);
+}
+
+TEST(GlobalNodeGraphTest, VisitInDepthFirstPreOrder) {
+  GlobalNodeGraph graph;
+  Process* process_1 = graph.CreateGraphForProcess(1);
+  Process* process_2 = graph.CreateGraphForProcess(2);
+
+  Node* c1 = process_1->CreateNode(kEmptyId, "c1", false);
+  Node* c2 = process_1->CreateNode(kEmptyId, "c2", false);
+  Node* c2_c1 = process_1->CreateNode(kEmptyId, "c2/c1", false);
+  Node* c2_c2 = process_1->CreateNode(kEmptyId, "c2/c2", false);
+
+  Node* c3 = process_2->CreateNode(kEmptyId, "c3", false);
+  Node* c3_c1 = process_2->CreateNode(kEmptyId, "c3/c1", false);
+  Node* c3_c2 = process_2->CreateNode(kEmptyId, "c3/c2", false);
+
+  // |c2_c2| owns |c3_c2|. Note this is opposite of post-order.
+  graph.AddNodeOwnershipEdge(c2_c2, c3_c2, 1);
+
+  // This method should always call owners and then children after the node
+  // itself.
+  auto iterator = graph.VisitInDepthFirstPreOrder();
+  ASSERT_EQ(iterator.next(), graph.shared_memory_graph()->root());
+  ASSERT_EQ(iterator.next(), process_1->root());
+  ASSERT_EQ(iterator.next(), c1);
+  ASSERT_EQ(iterator.next(), c2);
+  ASSERT_EQ(iterator.next(), c2_c1);
+  ASSERT_EQ(iterator.next(), process_2->root());
+  ASSERT_EQ(iterator.next(), c3);
+  ASSERT_EQ(iterator.next(), c3_c1);
+  ASSERT_EQ(iterator.next(), c3_c2);
+  ASSERT_EQ(iterator.next(), c2_c2);
+  ASSERT_EQ(iterator.next(), nullptr);
+}
+
+TEST(ProcessTest, CreateAndFindNode) {
+  GlobalNodeGraph global_dump_graph;
+  Process graph(1, &global_dump_graph);
+
+  Node* first =
+      graph.CreateNode(MemoryAllocatorNodeId(1), "simple/test/1", false);
+  Node* second =
+      graph.CreateNode(MemoryAllocatorNodeId(2), "simple/test/2", false);
+  Node* third =
+      graph.CreateNode(MemoryAllocatorNodeId(3), "simple/other/1", false);
+  Node* fourth =
+      graph.CreateNode(MemoryAllocatorNodeId(4), "complex/path", false);
+  Node* fifth =
+      graph.CreateNode(MemoryAllocatorNodeId(5), "complex/path/child/1", false);
+
+  ASSERT_EQ(graph.FindNode("simple/test/1"), first);
+  ASSERT_EQ(graph.FindNode("simple/test/2"), second);
+  ASSERT_EQ(graph.FindNode("simple/other/1"), third);
+  ASSERT_EQ(graph.FindNode("complex/path"), fourth);
+  ASSERT_EQ(graph.FindNode("complex/path/child/1"), fifth);
+
+  auto& nodes_by_id = global_dump_graph.nodes_by_id();
+  ASSERT_EQ(nodes_by_id.find(MemoryAllocatorNodeId(1))->second, first);
+  ASSERT_EQ(nodes_by_id.find(MemoryAllocatorNodeId(2))->second, second);
+  ASSERT_EQ(nodes_by_id.find(MemoryAllocatorNodeId(3))->second, third);
+  ASSERT_EQ(nodes_by_id.find(MemoryAllocatorNodeId(4))->second, fourth);
+  ASSERT_EQ(nodes_by_id.find(MemoryAllocatorNodeId(5))->second, fifth);
+}
+
+TEST(ProcessTest, CreateNodeParent) {
+  GlobalNodeGraph global_dump_graph;
+  Process graph(1, &global_dump_graph);
+
+  Node* parent = graph.CreateNode(MemoryAllocatorNodeId(1), "simple", false);
+  Node* child =
+      graph.CreateNode(MemoryAllocatorNodeId(1), "simple/child", false);
+
+  ASSERT_EQ(parent->parent(), graph.root());
+  ASSERT_EQ(child->parent(), parent);
+}
+
+TEST(ProcessTest, WeakAndExplicit) {
+  GlobalNodeGraph global_dump_graph;
+  Process graph(1, &global_dump_graph);
+
+  Node* first =
+      graph.CreateNode(MemoryAllocatorNodeId(1), "simple/test/1", true);
+  Node* second =
+      graph.CreateNode(MemoryAllocatorNodeId(2), "simple/test/2", false);
+
+  ASSERT_TRUE(first->is_weak());
+  ASSERT_FALSE(second->is_weak());
+
+  ASSERT_TRUE(first->is_explicit());
+  ASSERT_TRUE(second->is_explicit());
+
+  Node* parent = graph.FindNode("simple/test");
+  ASSERT_NE(parent, nullptr);
+  ASSERT_FALSE(parent->is_weak());
+  ASSERT_FALSE(parent->is_explicit());
+
+  Node* grandparent = graph.FindNode("simple");
+  ASSERT_NE(grandparent, nullptr);
+  ASSERT_FALSE(grandparent->is_weak());
+  ASSERT_FALSE(grandparent->is_explicit());
+}
+
+TEST(NodeTest, GetChild) {
+  GlobalNodeGraph global_dump_graph;
+  Node node(global_dump_graph.shared_memory_graph(), nullptr);
+
+  ASSERT_EQ(node.GetChild("test"), nullptr);
+
+  Node child(global_dump_graph.shared_memory_graph(), &node);
+  node.InsertChild("child", &child);
+  ASSERT_EQ(node.GetChild("child"), &child);
+}
+
+TEST(NodeTest, InsertChild) {
+  GlobalNodeGraph global_dump_graph;
+  Node node(global_dump_graph.shared_memory_graph(), nullptr);
+
+  ASSERT_EQ(node.GetChild("test"), nullptr);
+
+  Node child(global_dump_graph.shared_memory_graph(), &node);
+  node.InsertChild("child", &child);
+  ASSERT_EQ(node.GetChild("child"), &child);
+}
+
+TEST(NodeTest, AddEntry) {
+  GlobalNodeGraph global_dump_graph;
+  Node node(global_dump_graph.shared_memory_graph(), nullptr);
+
+  node.AddEntry("scalar", Node::Entry::ScalarUnits::kBytes, 100ul);
+  ASSERT_EQ(node.entries()->size(), 1ul);
+
+  node.AddEntry("string", "data");
+  ASSERT_EQ(node.entries()->size(), 2ul);
+
+  auto scalar = node.entries()->find("scalar");
+  ASSERT_EQ(scalar->first, "scalar");
+  ASSERT_EQ(scalar->second.units, Node::Entry::ScalarUnits::kBytes);
+  ASSERT_EQ(scalar->second.value_uint64, 100ul);
+
+  auto string = node.entries()->find("string");
+  ASSERT_EQ(string->first, "string");
+  ASSERT_EQ(string->second.value_string, "data");
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/memory_allocator_node_id.cc b/src/trace_processor/importers/memory_tracker/memory_allocator_node_id.cc
new file mode 100644
index 0000000..9d80101
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/memory_allocator_node_id.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/memory_allocator_node_id.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+MemoryAllocatorNodeId::MemoryAllocatorNodeId(uint64_t id) : id_(id) {}
+
+MemoryAllocatorNodeId::MemoryAllocatorNodeId() : MemoryAllocatorNodeId(0u) {}
+
+std::string MemoryAllocatorNodeId::ToString() const {
+  size_t max_size = 19;  // Max uint64 is 0xFFFFFFFFFFFFFFFF + 1 for null byte.
+  std::string buf;
+  buf.resize(max_size);
+  auto final_size = snprintf(&buf[0], max_size, "%" PRIu64, id_);
+  PERFETTO_DCHECK(final_size >= 0);
+  buf.resize(static_cast<size_t>(final_size));  // Cuts off the final null byte.
+  return buf;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/raw_memory_graph_node.cc b/src/trace_processor/importers/memory_tracker/raw_memory_graph_node.cc
new file mode 100644
index 0000000..74a9207
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/raw_memory_graph_node.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/raw_memory_graph_node.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+const char RawMemoryGraphNode::kNameSize[] = "size";
+const char RawMemoryGraphNode::kNameObjectCount[] = "object_count";
+const char RawMemoryGraphNode::kTypeScalar[] = "scalar";
+const char RawMemoryGraphNode::kTypeString[] = "string";
+const char RawMemoryGraphNode::kUnitsBytes[] = "bytes";
+const char RawMemoryGraphNode::kUnitsObjects[] = "objects";
+
+RawMemoryGraphNode::MemoryNodeEntry::MemoryNodeEntry(const std::string& n,
+                                                     const std::string& u,
+                                                     uint64_t v)
+    : name(n), units(u), entry_type(kUint64), value_uint64(v) {}
+
+RawMemoryGraphNode::MemoryNodeEntry::MemoryNodeEntry(const std::string& n,
+                                                     const std::string& u,
+                                                     const std::string& v)
+    : name(n), units(u), entry_type(kString), value_string(v) {}
+
+bool RawMemoryGraphNode::MemoryNodeEntry::operator==(
+    const MemoryNodeEntry& rhs) const {
+  if (!(name == rhs.name && units == rhs.units && entry_type == rhs.entry_type))
+    return false;
+  switch (entry_type) {
+    case EntryType::kUint64:
+      return value_uint64 == rhs.value_uint64;
+    case EntryType::kString:
+      return value_string == rhs.value_string;
+  }
+  return false;
+}
+
+RawMemoryGraphNode::RawMemoryGraphNode(const std::string& absolute_name,
+                                       LevelOfDetail level,
+                                       MemoryAllocatorNodeId id)
+    : absolute_name_(absolute_name),
+      level_of_detail_(level),
+      id_(id),
+      flags_(Flags::kDefault) {}
+
+RawMemoryGraphNode::RawMemoryGraphNode(
+    const std::string& absolute_name,
+    LevelOfDetail level,
+    MemoryAllocatorNodeId id,
+    std::vector<RawMemoryGraphNode::MemoryNodeEntry>&& entries)
+    : absolute_name_(absolute_name),
+      level_of_detail_(level),
+      entries_(std::move(entries)),
+      id_(id),
+      flags_(Flags::kDefault) {}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/raw_process_memory_node.cc b/src/trace_processor/importers/memory_tracker/raw_process_memory_node.cc
new file mode 100644
index 0000000..faaf084
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/raw_process_memory_node.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <functional>
+#include <memory>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+RawProcessMemoryNode::RawProcessMemoryNode(LevelOfDetail level_of_detail,
+                                           AllocatorNodeEdgesMap&& edges_map,
+                                           MemoryNodesMap&& nodes_map)
+    : level_of_detail_(level_of_detail),
+      allocator_nodes_edges_(std::move(edges_map)),
+      allocator_nodes_(std::move(nodes_map)) {}
+
+RawProcessMemoryNode::RawProcessMemoryNode(RawProcessMemoryNode&&) = default;
+RawProcessMemoryNode::~RawProcessMemoryNode() = default;
+RawProcessMemoryNode& RawProcessMemoryNode::operator=(RawProcessMemoryNode&&) =
+    default;
+
+RawMemoryGraphNode* RawProcessMemoryNode::GetAllocatorNode(
+    const std::string& absolute_name) const {
+  auto it = allocator_nodes_.find(absolute_name);
+  if (it != allocator_nodes_.end())
+    return it->second.get();
+  return nullptr;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/memory_tracker/raw_process_memory_node_unittest.cc b/src/trace_processor/importers/memory_tracker/raw_process_memory_node_unittest.cc
new file mode 100644
index 0000000..91a27ec
--- /dev/null
+++ b/src/trace_processor/importers/memory_tracker/raw_process_memory_node_unittest.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/raw_process_memory_node.h"
+
+#include <stddef.h>
+
+#include <unordered_map>
+
+#include "perfetto/base/build_config.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+namespace {
+
+const LevelOfDetail kLevelOfDetail = LevelOfDetail::kDetailed;
+
+}  // namespace
+
+TEST(RawProcessMemoryNodeTest, MoveConstructor) {
+  const auto source = MemoryAllocatorNodeId(42);
+  const auto target = MemoryAllocatorNodeId(4242);
+
+  std::unique_ptr<RawMemoryGraphNode> mad1(
+      new RawMemoryGraphNode("mad1", kLevelOfDetail, source));
+  std::unique_ptr<RawMemoryGraphNode> mad2(
+      new RawMemoryGraphNode("mad2", kLevelOfDetail, target));
+
+  RawProcessMemoryNode::MemoryNodesMap nodesMap;
+  nodesMap.emplace(mad1->absolute_name(), std::move(mad1));
+  nodesMap.emplace(mad2->absolute_name(), std::move(mad2));
+
+  std::unique_ptr<MemoryGraphEdge> edge(
+      new MemoryGraphEdge(source, target, 10, false));
+
+  RawProcessMemoryNode::AllocatorNodeEdgesMap edgesMap;
+  edgesMap.emplace(edge->source, std::move(edge));
+
+  RawProcessMemoryNode pmd1(kLevelOfDetail, std::move(edgesMap),
+                            std::move(nodesMap));
+
+  RawProcessMemoryNode pmd2(std::move(pmd1));
+
+  EXPECT_EQ(1u, pmd2.allocator_nodes().count("mad1"));
+  EXPECT_EQ(1u, pmd2.allocator_nodes().count("mad2"));
+  EXPECT_EQ(LevelOfDetail::kDetailed, pmd2.level_of_detail());
+  EXPECT_EQ(1u, pmd2.allocator_nodes_edges().size());
+}
+
+TEST(RawProcessMemoryNodeTest, MoveAssignment) {
+  const auto source = MemoryAllocatorNodeId(42);
+  const auto target = MemoryAllocatorNodeId(4242);
+
+  std::unique_ptr<RawMemoryGraphNode> mad1(
+      new RawMemoryGraphNode("mad1", kLevelOfDetail, source));
+  std::unique_ptr<RawMemoryGraphNode> mad2(
+      new RawMemoryGraphNode("mad2", kLevelOfDetail, target));
+
+  RawProcessMemoryNode::MemoryNodesMap nodesMap;
+  nodesMap.emplace(mad1->absolute_name(), std::move(mad1));
+  nodesMap.emplace(mad2->absolute_name(), std::move(mad2));
+
+  std::unique_ptr<MemoryGraphEdge> edge(
+      new MemoryGraphEdge(source, target, 10, false));
+
+  RawProcessMemoryNode::AllocatorNodeEdgesMap edgesMap;
+  edgesMap.emplace(edge->source, std::move(edge));
+
+  RawProcessMemoryNode pmd1(kLevelOfDetail, std::move(edgesMap),
+                            std::move(nodesMap));
+
+  RawProcessMemoryNode pmd2(LevelOfDetail::kBackground);
+
+  pmd2 = std::move(pmd1);
+  EXPECT_EQ(1u, pmd2.allocator_nodes().count("mad1"));
+  EXPECT_EQ(1u, pmd2.allocator_nodes().count("mad2"));
+  EXPECT_EQ(0u, pmd2.allocator_nodes().count("mad3"));
+  EXPECT_EQ(LevelOfDetail::kDetailed, pmd2.level_of_detail());
+  EXPECT_EQ(1u, pmd2.allocator_nodes_edges().size());
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/args_table_utils.h b/src/trace_processor/importers/proto/args_table_utils.h
index db06732..88e00b2 100644
--- a/src/trace_processor/importers/proto/args_table_utils.h
+++ b/src/trace_processor/importers/proto/args_table_utils.h
@@ -107,7 +107,7 @@
   //
   // To generate |proto_descriptor_array| please see
   // tools/gen_binary_descriptors and ensure the proto you are interested in is
-  // listed as a whitelisted proto. You can then find your variable inside the
+  // listed in the event_list file. You can then find your variable inside the
   // header location specified inside that python script.
   util::Status AddProtoFileDescriptor(const uint8_t* proto_descriptor_array,
                                       size_t proto_descriptor_array_size);
diff --git a/src/trace_processor/importers/proto/args_table_utils_unittest.cc b/src/trace_processor/importers/proto/args_table_utils_unittest.cc
index 1e04141..8a3f5d1 100644
--- a/src/trace_processor/importers/proto/args_table_utils_unittest.cc
+++ b/src/trace_processor/importers/proto/args_table_utils_unittest.cc
@@ -137,7 +137,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.EveryField", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
 
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
@@ -214,7 +214,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.NestedA", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
       << status.message();
@@ -243,7 +243,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.CamelCaseFields", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
       << status.message();
@@ -287,7 +287,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.NestedA", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
       << status.message();
@@ -328,7 +328,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.NestedA", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
       << status.message();
@@ -394,7 +394,7 @@
   status = helper.InternProtoFieldsIntoArgsTable(
       protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
       ".protozero.test.protos.NestedA", nullptr, &inserter,
-      sequence_state_->current_generation());
+      sequence_state_->current_generation().get());
   EXPECT_TRUE(status.ok())
       << "InternProtoFieldsIntoArgsTable failed with error: "
       << status.message();
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.cc b/src/trace_processor/importers/proto/async_track_set_tracker.cc
new file mode 100644
index 0000000..5847d2d
--- /dev/null
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.cc
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
+
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+AsyncTrackSetTracker::AsyncTrackSetTracker(TraceProcessorContext* context)
+    : context_(context) {}
+
+AsyncTrackSetTracker::TrackSetId AsyncTrackSetTracker::InternAndroidSet(
+    UniquePid upid,
+    StringId name) {
+  AndroidTuple tuple{upid, name};
+
+  auto it = android_track_set_ids_.find(tuple);
+  if (it != android_track_set_ids_.end())
+    return it->second;
+
+  uint32_t id = static_cast<uint32_t>(track_sets_.size());
+
+  TrackSet set;
+  set.android_tuple = tuple;
+  set.type = TrackSetType::kAndroid;
+  set.nesting_behaviour = NestingBehaviour::kLegacySaturatingUnnestable;
+  track_sets_.emplace_back(set);
+
+  android_track_set_ids_[tuple] = id;
+  return id;
+}
+
+TrackId AsyncTrackSetTracker::Begin(TrackSetId id, int64_t cookie) {
+  PERFETTO_DCHECK(id < track_sets_.size());
+
+  TrackSet& set = track_sets_[id];
+  TrackState& state = GetOrCreateTrackForCookie(set, cookie);
+  switch (set.nesting_behaviour) {
+    case NestingBehaviour::kLegacySaturatingUnnestable:
+      PERFETTO_DCHECK(state.nest_count <= 1);
+      state.nest_count = 1;
+      break;
+    case NestingBehaviour::kUnnestable:
+      PERFETTO_DCHECK(state.nest_count == 0);
+      state.nest_count++;
+      break;
+  }
+  return state.id;
+}
+
+TrackId AsyncTrackSetTracker::End(TrackSetId id, int64_t cookie) {
+  PERFETTO_DCHECK(id < track_sets_.size());
+
+  TrackSet& set = track_sets_[id];
+  TrackState& state = GetOrCreateTrackForCookie(set, cookie);
+
+  // It's possible to have a nest count of 0 even when we know about the track.
+  // Suppose the following sequence of events for some |id| and |cookie|:
+  //   Begin
+  //   (trace starts)
+  //   Begin
+  //   End
+  //   End <- nest count == 0 here even though we have a record of this track.
+  if (state.nest_count > 0)
+    state.nest_count--;
+  return state.id;
+}
+
+TrackId AsyncTrackSetTracker::Scoped(TrackSetId id, int64_t ts, int64_t dur) {
+  PERFETTO_DCHECK(id < track_sets_.size());
+
+  TrackSet& set = track_sets_[id];
+  PERFETTO_DCHECK(set.nesting_behaviour == NestingBehaviour::kUnnestable);
+
+  auto it = std::find_if(
+      set.tracks.begin(), set.tracks.end(), [ts](const TrackState& state) {
+        return state.slice_type == TrackState::SliceType::kTimestamp &&
+               state.ts_end <= ts;
+      });
+  if (it != set.tracks.end())
+    return it->id;
+
+  TrackState state;
+  state.slice_type = TrackState::SliceType::kTimestamp;
+  state.ts_end = ts + dur;
+  state.id = CreateTrackForSet(set);
+  set.tracks.emplace_back(state);
+
+  return state.id;
+}
+
+AsyncTrackSetTracker::TrackState&
+AsyncTrackSetTracker::GetOrCreateTrackForCookie(TrackSet& set, int64_t cookie) {
+  auto it = std::find_if(
+      set.tracks.begin(), set.tracks.end(), [cookie](const TrackState& state) {
+        return state.slice_type == TrackState::SliceType::kCookie &&
+               state.cookie == cookie;
+      });
+  if (it != set.tracks.end())
+    return *it;
+
+  it = std::find_if(
+      set.tracks.begin(), set.tracks.end(), [](const TrackState& state) {
+        return state.slice_type == TrackState::SliceType::kCookie &&
+               state.nest_count == 0;
+      });
+  if (it != set.tracks.end()) {
+    // Adopt this track for the cookie to make sure future slices with this
+    // cookie also get associated to this track.
+    it->cookie = cookie;
+    return *it;
+  }
+
+  TrackState state;
+  state.id = CreateTrackForSet(set);
+  state.slice_type = TrackState::SliceType::kCookie;
+  state.cookie = cookie;
+  state.nest_count = 0;
+  set.tracks.emplace_back(state);
+
+  return set.tracks.back();
+}
+
+TrackId AsyncTrackSetTracker::CreateTrackForSet(const TrackSet& set) {
+  switch (set.type) {
+    case TrackSetType::kAndroid:
+      return context_->track_tracker->CreateAndroidAsyncTrack(
+          set.android_tuple.name, set.android_tuple.upid);
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker.h b/src/trace_processor/importers/proto/async_track_set_tracker.h
new file mode 100644
index 0000000..469217d
--- /dev/null
+++ b/src/trace_processor/importers/proto/async_track_set_tracker.h
@@ -0,0 +1,171 @@
+/*
+ * 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_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
+
+#include <unordered_map>
+
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+class AsyncTrackSetTrackerUnittest;
+
+// Tracker used to reduce the number of trace processor tracks corresponding
+// to a single "UI track".
+//
+// UIs using trace processor want to display all slices in the same context
+// (e.g. same upid) and same name into a single track. However, because trace
+// processor does not allow parallel slices on a single track (because it breaks
+// things like span join, self time computation etc.), at the trace processor
+// level these parallel slices are put on different tracks.
+//
+// Creating a new track for every event, however, leads to an explosion of
+// tracks which is undesirable. This class exists to multiplex slices so that
+// n events correspond to a single track in a way which minimises the number of
+// tracks which needs to be merged by the UI.
+//
+// The intended usage of this class is for callers to first call one of the
+// Intern* methods to obtain a TrackSetId followed by Begin/End just before
+// calling into SliceTracker's Begin/End respectively. For example:
+//  TrackSetId set_id = track_set_tracker->InternAndroidSet(upid, name);
+//  if (event.begin) {
+//    TrackId id = track_set_tracker->Begin(set_id, cookie);
+//    slice_tracker->Begin(ts, id, ...)
+//  } else {
+//    ... (same thing with end)
+//  }
+// Alternatively, instead of Begin/End, Scoped can also be called if supported
+// by the track type.
+class AsyncTrackSetTracker {
+ public:
+  using TrackSetId = uint32_t;
+
+  explicit AsyncTrackSetTracker(TraceProcessorContext* context);
+  ~AsyncTrackSetTracker() = default;
+
+  // Interns a set of Android async slice tracks associated with the given
+  // upid and name.
+  // Scoped is *not* supported for this track set type.
+  TrackSetId InternAndroidSet(UniquePid, StringId name);
+
+  // Starts a new slice on the given async track set which has the given cookie.
+  TrackId Begin(TrackSetId id, int64_t cookie);
+
+  // Ends a new slice on the given async track set which has the given cookie.
+  TrackId End(TrackSetId id, int64_t cookie);
+
+  // Creates a scoped slice on the given async track set.
+  // This method makes sure that any other slice in this track set does
+  // not happen simultaneously on the returned track.
+  // Only supported on selected track set types; read the documentation for
+  // the Intern* method for your track type to check if supported.
+  TrackId Scoped(TrackSetId id, int64_t ts, int64_t dur);
+
+ private:
+  friend class AsyncTrackSetTrackerUnittest;
+
+  struct AndroidTuple {
+    UniquePid upid;
+    StringId name;
+
+    friend bool operator<(const AndroidTuple& l, const AndroidTuple& r) {
+      return std::tie(l.upid, l.name) < std::tie(r.upid, r.name);
+    }
+  };
+
+  // Indicates the nesting behaviour of slices associated to a single slice
+  // stack.
+  enum class NestingBehaviour {
+    // Indicates that slices are unnestable; that is, it is an error
+    // to call Begin -> Begin with a single cookie without End inbetween.
+    // This pattern should be the default behaviour that most async slices
+    // should use.
+    kUnnestable,
+
+    // Indicates that slices are unnestable but also saturating; that is
+    // calling Begin -> Begin only causes a single Begin to be recorded.
+    // This is only really useful for Android async slices which have this
+    // behaviour for legacy reasons. See the comment in
+    // SystraceParser::ParseSystracePoint for information on why
+    // this behaviour exists.
+    kLegacySaturatingUnnestable,
+  };
+
+  enum class TrackSetType {
+    kAndroid,
+  };
+
+  struct TrackState {
+    TrackId id;
+
+    enum class SliceType { kCookie, kTimestamp };
+    SliceType slice_type;
+
+    union {
+      // Only valid for |slice_type| == |SliceType::kCookie|.
+      int64_t cookie;
+
+      // Only valid for |slice_type| == |SliceType::kTimestamp|.
+      int64_t ts_end;
+    };
+
+    // Only used for |slice_type| == |SliceType::kCookie|.
+    uint32_t nest_count;
+  };
+
+  struct TrackSet {
+    TrackSetType type;
+    union {
+      // Only set when |type| == |TrackSetType::kAndroid|.
+      AndroidTuple android_tuple;
+    };
+    NestingBehaviour nesting_behaviour;
+    std::vector<TrackState> tracks;
+  };
+
+  TrackSetId CreateUnnestableTrackSetForTesting(UniquePid upid, StringId name) {
+    AsyncTrackSetTracker::TrackSet set;
+    set.android_tuple = AndroidTuple{upid, name};
+    set.type = AsyncTrackSetTracker::TrackSetType::kAndroid;
+    set.nesting_behaviour = NestingBehaviour::kUnnestable;
+    track_sets_.emplace_back(set);
+    return static_cast<TrackSetId>(track_sets_.size() - 1);
+  }
+
+  // Returns the state for a track using the following algorithm:
+  // 1. If a track exists with the given cookie in the track set, returns
+  //    that track.
+  // 2. Otherwise, looks for any track in the set which is "open" (i.e.
+  //    does not have another slice currently scheduled).
+  // 3. Otherwise, creates a new track and associates it with the set.
+  TrackState& GetOrCreateTrackForCookie(TrackSet& set, int64_t cookie);
+
+  TrackId CreateTrackForSet(const TrackSet& set);
+
+  std::map<AndroidTuple, TrackSetId> android_track_set_ids_;
+  std::vector<TrackSet> track_sets_;
+
+  TraceProcessorContext* const context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_ASYNC_TRACK_SET_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
new file mode 100644
index 0000000..852c5c2
--- /dev/null
+++ b/src/trace_processor/importers/proto/async_track_set_tracker_unittest.cc
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
+
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class AsyncTrackSetTrackerUnittest : public testing::Test {
+ public:
+  AsyncTrackSetTrackerUnittest() {
+    context_.storage.reset(new TraceStorage());
+    context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+    context_.args_tracker.reset(new ArgsTracker(&context_));
+    context_.track_tracker.reset(new TrackTracker(&context_));
+    context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
+
+    storage_ = context_.storage.get();
+    tracker_ = context_.async_track_set_tracker.get();
+
+    unnestable_id_ = tracker_->CreateUnnestableTrackSetForTesting(
+        1, storage_->InternString("test"));
+    legacy_unnestable_id_ =
+        tracker_->InternAndroidSet(2, storage_->InternString("test"));
+  }
+
+ protected:
+  TraceStorage* storage_ = nullptr;
+  AsyncTrackSetTracker* tracker_ = nullptr;
+
+  AsyncTrackSetTracker::TrackSetId unnestable_id_;
+  AsyncTrackSetTracker::TrackSetId legacy_unnestable_id_;
+
+ private:
+  TraceProcessorContext context_;
+};
+
+namespace {
+
+TEST_F(AsyncTrackSetTrackerUnittest, Smoke) {
+  auto set_id = tracker_->InternAndroidSet(1, storage_->InternString("test"));
+
+  auto begin = tracker_->Begin(set_id, 1);
+  auto end = tracker_->End(set_id, 1);
+
+  ASSERT_EQ(begin, end);
+
+  uint32_t row = *storage_->process_track_table().id().IndexOf(begin);
+  ASSERT_EQ(storage_->process_track_table().upid()[row], 1u);
+  ASSERT_EQ(storage_->process_track_table().name()[row],
+            storage_->InternString("test"));
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, EndFirst) {
+  auto end = tracker_->End(unnestable_id_, 1);
+
+  uint32_t row = *storage_->process_track_table().id().IndexOf(end);
+  ASSERT_EQ(storage_->process_track_table().upid()[row], 1u);
+  ASSERT_EQ(storage_->process_track_table().name()[row],
+            storage_->InternString("test"));
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, LegacySaturating) {
+  auto begin = tracker_->Begin(legacy_unnestable_id_, 1);
+  auto begin_2 = tracker_->Begin(legacy_unnestable_id_, 1);
+
+  ASSERT_EQ(begin, begin_2);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, Unnestable) {
+  auto begin = tracker_->Begin(unnestable_id_, 1);
+  auto end = tracker_->End(unnestable_id_, 1);
+  auto begin_2 = tracker_->Begin(unnestable_id_, 1);
+
+  ASSERT_EQ(begin, end);
+  ASSERT_EQ(begin, begin_2);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, UnnestableMultipleEndAfterBegin) {
+  auto begin = tracker_->Begin(unnestable_id_, 1);
+  auto end = tracker_->End(unnestable_id_, 1);
+  auto end_2 = tracker_->End(unnestable_id_, 1);
+
+  ASSERT_EQ(begin, end);
+  ASSERT_EQ(end, end_2);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, OnlyScoped) {
+  TrackId a = tracker_->Scoped(unnestable_id_, 100, 10);
+  TrackId b = tracker_->Scoped(unnestable_id_, 105, 2);
+  TrackId c = tracker_->Scoped(unnestable_id_, 107, 3);
+  TrackId d = tracker_->Scoped(unnestable_id_, 110, 5);
+
+  ASSERT_NE(a, b);
+  ASSERT_EQ(b, c);
+  ASSERT_EQ(a, d);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, MixScopedAndBeginEnd) {
+  TrackId a = tracker_->Scoped(unnestable_id_, 100, 10);
+
+  TrackId begin = tracker_->Begin(unnestable_id_, 777);
+  TrackId end = tracker_->End(unnestable_id_, 777);
+
+  TrackId b = tracker_->Scoped(unnestable_id_, 105, 2);
+
+  ASSERT_NE(a, begin);
+  ASSERT_NE(b, begin);
+  ASSERT_EQ(begin, end);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, DifferentTracksInterleave) {
+  TrackId b1 = tracker_->Begin(unnestable_id_, 666);
+  TrackId b2 = tracker_->Begin(legacy_unnestable_id_, 777);
+  TrackId e1 = tracker_->End(unnestable_id_, 666);
+  TrackId e2 = tracker_->End(legacy_unnestable_id_, 777);
+
+  ASSERT_EQ(b1, e1);
+  ASSERT_EQ(b2, e2);
+  ASSERT_NE(b1, b2);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, DifferentCookieInterleave) {
+  TrackId b1 = tracker_->Begin(legacy_unnestable_id_, 666);
+  TrackId b2 = tracker_->Begin(legacy_unnestable_id_, 777);
+  TrackId e1 = tracker_->End(legacy_unnestable_id_, 666);
+  TrackId e2 = tracker_->End(legacy_unnestable_id_, 777);
+
+  ASSERT_EQ(b1, e1);
+  ASSERT_EQ(b2, e2);
+  ASSERT_NE(b1, b2);
+}
+
+TEST_F(AsyncTrackSetTrackerUnittest, DifferentCookieSequential) {
+  TrackId b1 = tracker_->Begin(legacy_unnestable_id_, 666);
+  TrackId e1 = tracker_->End(legacy_unnestable_id_, 666);
+  TrackId b2 = tracker_->Begin(legacy_unnestable_id_, 777);
+  TrackId e2 = tracker_->End(legacy_unnestable_id_, 777);
+
+  ASSERT_EQ(b1, e1);
+  ASSERT_EQ(b1, b2);
+  ASSERT_EQ(b2, e2);
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 2989b09..c46b26f 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -112,7 +112,14 @@
                              "UNKNOWN_SEVERITY") /* must be last */}},
       vk_event_track_id_(context->storage->InternString("Vulkan Events")),
       vk_event_scope_id_(context->storage->InternString("vulkan_events")),
-      vk_queue_submit_id_(context->storage->InternString("vkQueueSubmit")) {}
+      vk_queue_submit_id_(context->storage->InternString("vkQueueSubmit")),
+      gpu_mem_total_name_id_(context->storage->InternString("GPU Memory")),
+      gpu_mem_total_unit_id_(context->storage->InternString(
+          std::to_string(protos::pbzero::GpuCounterDescriptor::BYTE).c_str())),
+      gpu_mem_total_global_desc_id_(context->storage->InternString(
+          "Total GPU memory used by the entire system")),
+      gpu_mem_total_proc_desc_id_(context->storage->InternString(
+          "Total GPU memory used by this process")) {}
 
 void GpuEventParser::ParseGpuCounterEvent(int64_t ts, ConstBytes blob) {
   protos::pbzero::GpuCounterEvent::Decoder event(blob.data, blob.size);
@@ -226,13 +233,13 @@
     }
     stage_name = context_->storage->InternString(decoder->name());
   } else {
-    size_t stage_id = static_cast<size_t>(event.stage_id());
+    uint64_t stage_id = static_cast<uint64_t>(event.stage_id());
 
     if (stage_id < gpu_render_stage_ids_.size()) {
-      stage_name = gpu_render_stage_ids_[stage_id].first;
+      stage_name = gpu_render_stage_ids_[static_cast<size_t>(stage_id)].first;
     } else {
       char buffer[64];
-      snprintf(buffer, sizeof(buffer), "render stage(%zu)", stage_id);
+      snprintf(buffer, sizeof(buffer), "render stage(%" PRIu64 ")", stage_id);
       stage_name = context_->storage->InternString(buffer);
     }
   }
@@ -728,5 +735,27 @@
   }
 }
 
+void GpuEventParser::ParseGpuMemTotalEvent(int64_t ts, ConstBytes blob) {
+  protos::pbzero::GpuMemTotalEvent::Decoder gpu_mem_total(blob.data, blob.size);
+
+  TrackId track = kInvalidTrackId;
+  const uint32_t pid = gpu_mem_total.pid();
+  if (pid == 0) {
+    // Pid 0 is used to indicate the global total
+    track = context_->track_tracker->InternGlobalCounterTrack(
+        gpu_mem_total_name_id_, gpu_mem_total_unit_id_,
+        gpu_mem_total_global_desc_id_);
+  } else {
+    // Process emitting the packet can be different from the pid in the event.
+    UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
+    UniquePid upid = context_->storage->thread_table().upid()[utid].value_or(0);
+    track = context_->track_tracker->InternProcessCounterTrack(
+        gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
+        gpu_mem_total_proc_desc_id_);
+  }
+  context_->event_tracker->PushCounter(
+      ts, static_cast<double>(gpu_mem_total.size()), track);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.h b/src/trace_processor/importers/proto/gpu_event_parser.h
index 34aaa43..b79b65f 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.h
+++ b/src/trace_processor/importers/proto/gpu_event_parser.h
@@ -22,6 +22,7 @@
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_writer.h"
 #include "perfetto/protozero/field.h"
+#include "protos/perfetto/trace/android/gpu_mem_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/proto/proto_incremental_state.h"
@@ -72,6 +73,8 @@
 
   void ParseVulkanApiEvent(int64_t, ConstBytes);
 
+  void ParseGpuMemTotalEvent(int64_t, ConstBytes);
+
  private:
   const StringId GetFullStageName(
       PacketSequenceStateGeneration* sequence_state,
@@ -120,6 +123,11 @@
   StringId vk_event_track_id_;
   StringId vk_event_scope_id_;
   StringId vk_queue_submit_id_;
+  // For GpuMemTotalEvent
+  const StringId gpu_mem_total_name_id_;
+  const StringId gpu_mem_total_unit_id_;
+  const StringId gpu_mem_total_global_desc_id_;
+  const StringId gpu_mem_total_proc_desc_id_;
 };
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/graphics_event_module.cc b/src/trace_processor/importers/proto/graphics_event_module.cc
index 5315705..88b9381 100644
--- a/src/trace_processor/importers/proto/graphics_event_module.cc
+++ b/src/trace_processor/importers/proto/graphics_event_module.cc
@@ -26,6 +26,7 @@
   RegisterForField(TracePacket::kGpuCounterEventFieldNumber, context);
   RegisterForField(TracePacket::kGpuRenderStageEventFieldNumber, context);
   RegisterForField(TracePacket::kGpuLogFieldNumber, context);
+  RegisterForField(TracePacket::kGpuMemTotalEventFieldNumber, context);
   RegisterForField(TracePacket::kGraphicsFrameEventFieldNumber, context);
   RegisterForField(TracePacket::kVulkanMemoryEventFieldNumber, context);
   RegisterForField(TracePacket::kVulkanApiEventFieldNumber, context);
@@ -42,7 +43,7 @@
       return;
     case TracePacket::kGpuRenderStageEventFieldNumber:
       parser_.ParseGpuRenderStageEvent(ttp.timestamp,
-                                       ttp.packet_data.sequence_state,
+                                       ttp.packet_data.sequence_state.get(),
                                        decoder.gpu_render_stage_event());
       return;
     case TracePacket::kGpuLogFieldNumber:
@@ -54,12 +55,16 @@
       return;
     case TracePacket::kVulkanMemoryEventFieldNumber:
       PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTracePacket);
-      parser_.ParseVulkanMemoryEvent(ttp.packet_data.sequence_state,
+      parser_.ParseVulkanMemoryEvent(ttp.packet_data.sequence_state.get(),
                                      decoder.vulkan_memory_event());
       return;
     case TracePacket::kVulkanApiEventFieldNumber:
       parser_.ParseVulkanApiEvent(ttp.timestamp, decoder.vulkan_api_event());
       return;
+    case TracePacket::kGpuMemTotalEventFieldNumber:
+      parser_.ParseGpuMemTotalEvent(ttp.timestamp,
+                                    decoder.gpu_mem_total_event());
+      return;
   }
 }
 
diff --git a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
index e33ed9c..35e27f9 100644
--- a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
+++ b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
@@ -161,6 +161,31 @@
   return true;
 }
 
+void GraphicsFrameEventParser::InvalidatePhaseEvent(int64_t timestamp,
+                                                    TrackId track_id,
+                                                    bool reset_name) {
+  const auto opt_slice_id =
+      context_->slice_tracker->EndFrameEvent(timestamp, track_id);
+
+  if (opt_slice_id) {
+    auto* graphics_frame_slice_table =
+        context_->storage->mutable_graphics_frame_slice_table();
+    uint32_t row_idx = *graphics_frame_slice_table->id().IndexOf(*opt_slice_id);
+    if (reset_name) {
+      // Set the name (frame_number) to be 0 since there is no frame number
+      // associated, example : dequeue event.
+      StringId frame_name_id = context_->storage->InternString("0");
+      graphics_frame_slice_table->mutable_name()->Set(row_idx, frame_name_id);
+      graphics_frame_slice_table->mutable_frame_number()->Set(row_idx, 0);
+    }
+
+    // Set the duration to -1 so that this slice will be ignored by the
+    // UI. Setting any other duration results in wrong data which we want
+    // to avoid at all costs.
+    graphics_frame_slice_table->mutable_dur()->Set(row_idx, -1);
+  }
+}
+
 // Here we convert the buffer events into Phases(slices)
 // APP: Dequeue to Queue
 // Wait for GPU: Queue to Acquire
@@ -200,16 +225,29 @@
       tables::GpuTrackTable::Row app_track(track_name_id);
       app_track.scope = graphics_event_scope_id_;
       track_id = context_->track_tracker->InternGpuTrack(app_track);
+
+      // Error handling
+      auto dequeue_time = dequeue_map_.find(buffer_id);
+      if (dequeue_time != dequeue_map_.end()) {
+        InvalidatePhaseEvent(timestamp, dequeue_time->second, true);
+        dequeue_map_.erase(dequeue_time);
+      }
+      auto queue_time = queue_map_.find(buffer_id);
+      if (queue_time != queue_map_.end()) {
+        InvalidatePhaseEvent(timestamp, queue_time->second);
+        queue_map_.erase(queue_time);
+      }
+
       dequeue_map_[buffer_id] = track_id;
       last_dequeued_[buffer_id] = timestamp;
       break;
     }
 
     case GraphicsFrameEvent::QUEUE: {
-      auto dequeueTime = dequeue_map_.find(buffer_id);
-      if (dequeueTime != dequeue_map_.end()) {
+      auto dequeue_time = dequeue_map_.find(buffer_id);
+      if (dequeue_time != dequeue_map_.end()) {
         const auto opt_slice_id = context_->slice_tracker->EndFrameEvent(
-            timestamp, dequeueTime->second);
+            timestamp, dequeue_time->second);
         slice_name.reset();
         slice_name.AppendUnsignedInt(frame_number);
         if (opt_slice_id) {
@@ -225,7 +263,7 @@
                                                           frame_name_id);
           graphics_frame_slice_table->mutable_frame_number()->Set(row_idx,
                                                                   frame_number);
-          dequeue_map_.erase(dequeueTime);
+          dequeue_map_.erase(dequeue_time);
         }
       }
       // The AcquireFence might be signaled before receiving a QUEUE event
@@ -247,10 +285,10 @@
       break;
     }
     case GraphicsFrameEvent::ACQUIRE_FENCE: {
-      auto queueTime = queue_map_.find(buffer_id);
-      if (queueTime != queue_map_.end()) {
-        context_->slice_tracker->EndFrameEvent(timestamp, queueTime->second);
-        queue_map_.erase(queueTime);
+      auto queue_time = queue_map_.find(buffer_id);
+      if (queue_time != queue_map_.end()) {
+        context_->slice_tracker->EndFrameEvent(timestamp, queue_time->second);
+        queue_map_.erase(queue_time);
       }
       last_acquired_[buffer_id] = timestamp;
       start_slice = false;
@@ -259,31 +297,10 @@
     case GraphicsFrameEvent::LATCH: {
       // b/157578286 - Sometimes Queue event goes missing. To prevent having a
       // wrong slice info, we try to close any existing APP slice.
-      auto dequeueTime = dequeue_map_.find(buffer_id);
-      if (dequeueTime != dequeue_map_.end()) {
-        auto args_callback = [this](ArgsTracker::BoundInserter* inserter) {
-          inserter->AddArg(context_->storage->InternString("Details"),
-                           Variadic::String(queue_lost_message_id_));
-        };
-        const auto opt_slice_id = context_->slice_tracker->EndFrameEvent(
-            timestamp, dequeueTime->second, args_callback);
-        slice_name.reset();
-        slice_name.AppendUnsignedInt(frame_number);
-        if (opt_slice_id) {
-          auto* graphics_frame_slice_table =
-              context_->storage->mutable_graphics_frame_slice_table();
-          // Set the name of the slice to be the frame number since dequeue did
-          // not have a frame number at that time.
-          uint32_t row_idx =
-              *graphics_frame_slice_table->id().IndexOf(*opt_slice_id);
-          StringId frame_name_id =
-              context_->storage->InternString(slice_name.GetStringView());
-          graphics_frame_slice_table->mutable_name()->Set(row_idx,
-                                                          frame_name_id);
-          graphics_frame_slice_table->mutable_frame_number()->Set(row_idx,
-                                                                  frame_number);
-          dequeue_map_.erase(dequeueTime);
-        }
+      auto dequeue_time = dequeue_map_.find(buffer_id);
+      if (dequeue_time != dequeue_map_.end()) {
+        InvalidatePhaseEvent(timestamp, dequeue_time->second, true);
+        dequeue_map_.erase(dequeue_time);
       }
       track_name.reset();
       track_name.AppendLiteral("SF_");
@@ -298,15 +315,15 @@
     }
 
     case GraphicsFrameEvent::PRESENT_FENCE: {
-      auto latchTime = latch_map_.find(buffer_id);
-      if (latchTime != latch_map_.end()) {
-        context_->slice_tracker->EndFrameEvent(timestamp, latchTime->second);
-        latch_map_.erase(latchTime);
+      auto latch_time = latch_map_.find(buffer_id);
+      if (latch_time != latch_map_.end()) {
+        context_->slice_tracker->EndFrameEvent(timestamp, latch_time->second);
+        latch_map_.erase(latch_time);
       }
-      auto displayTime = display_map_.find(layer_name_id);
-      if (displayTime != display_map_.end()) {
-        context_->slice_tracker->EndFrameEvent(timestamp, displayTime->second);
-        display_map_.erase(displayTime);
+      auto display_time = display_map_.find(layer_name_id);
+      if (display_time != display_map_.end()) {
+        context_->slice_tracker->EndFrameEvent(timestamp, display_time->second);
+        display_map_.erase(display_time);
       }
       base::StringView layerName(event.layer_name());
       track_name.reset();
diff --git a/src/trace_processor/importers/proto/graphics_frame_event_parser.h b/src/trace_processor/importers/proto/graphics_frame_event_parser.h
index e23fa73..b375c6c 100644
--- a/src/trace_processor/importers/proto/graphics_frame_event_parser.h
+++ b/src/trace_processor/importers/proto/graphics_frame_event_parser.h
@@ -49,6 +49,10 @@
   using GraphicsFrameEvent = protos::pbzero::GraphicsFrameEvent;
   bool CreateBufferEvent(int64_t timestamp, GraphicsFrameEventDecoder& event);
   void CreatePhaseEvent(int64_t timestamp, GraphicsFrameEventDecoder& event);
+  // Invalidate a phase slice that has one of the events missing
+  void InvalidatePhaseEvent(int64_t timestamp,
+                            TrackId track_id,
+                            bool reset_name = false);
 
   TraceProcessorContext* const context_;
   const StringId graphics_event_scope_id_;
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index dc0914d..fa3f36f 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -18,9 +18,11 @@
 
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
 #include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
 
 namespace perfetto {
@@ -127,18 +129,27 @@
     obj.self_size = object.self_size();
     obj.type_id = object.type_id();
 
-    std::vector<uint64_t> field_ids;
-    std::vector<uint64_t> object_ids;
+    uint64_t base_obj_id = object.reference_field_id_base();
 
+    // In S+ traces, this field will not be set for normal instances. It will be
+    // set in the corresponding HeapGraphType instead. It will still be set for
+    // class objects.
+    //
+    // grep-friendly: reference_field_id
     bool parse_error = ForEachVarInt<
         protos::pbzero::HeapGraphObject::kReferenceFieldIdFieldNumber>(
-        object, [&field_ids](uint64_t value) { field_ids.push_back(value); });
+        object,
+        [&obj](uint64_t value) { obj.field_name_ids.push_back(value); });
 
     if (!parse_error) {
+      // grep-friendly: reference_object_id
       parse_error = ForEachVarInt<
           protos::pbzero::HeapGraphObject::kReferenceObjectIdFieldNumber>(
-          object,
-          [&object_ids](uint64_t value) { object_ids.push_back(value); });
+          object, [&obj, base_obj_id](uint64_t value) {
+            if (value)
+              value += base_obj_id;
+            obj.referred_objects.push_back(value);
+          });
     }
 
     if (parse_error) {
@@ -146,35 +157,41 @@
           stats::heap_graph_malformed_packet, static_cast<int>(upid));
       break;
     }
-    if (field_ids.size() != object_ids.size()) {
+    if (!obj.field_name_ids.empty() &&
+        (obj.field_name_ids.size() != obj.referred_objects.size())) {
       context_->storage->IncrementIndexedStats(
           stats::heap_graph_malformed_packet, static_cast<int>(upid));
       continue;
     }
-    for (size_t i = 0; i < field_ids.size(); ++i) {
-      HeapGraphTracker::SourceObject::Reference ref;
-      ref.field_name_id = field_ids[i];
-      ref.owned_object_id = object_ids[i];
-      obj.references.emplace_back(std::move(ref));
-    }
     heap_graph_tracker->AddObject(seq_id, upid, ts, std::move(obj));
   }
   for (auto it = heap_graph.types(); it; ++it) {
+    std::vector<uint64_t> field_name_ids;
     protos::pbzero::HeapGraphType::Decoder entry(*it);
     const char* str = reinterpret_cast<const char*>(entry.class_name().data);
     auto str_view = base::StringView(str, entry.class_name().size);
 
-    heap_graph_tracker->AddInternedType(
-        seq_id, entry.id(), context_->storage->InternString(str_view),
-        entry.location_id());
-  }
-  for (auto it = heap_graph.type_names(); it; ++it) {
-    protos::pbzero::InternedString::Decoder entry(*it);
-    const char* str = reinterpret_cast<const char*>(entry.str().data);
-    auto str_view = base::StringView(str, entry.str().size);
+    // grep-friendly: reference_field_id
+    bool parse_error = ForEachVarInt<
+        protos::pbzero::HeapGraphType::kReferenceFieldIdFieldNumber>(
+        entry,
+        [&field_name_ids](uint64_t value) { field_name_ids.push_back(value); });
+
+    if (parse_error) {
+      context_->storage->IncrementIndexedStats(
+          stats::heap_graph_malformed_packet, static_cast<int>(upid));
+      continue;
+    }
+
+    bool no_fields =
+        entry.kind() == protos::pbzero::HeapGraphType::KIND_NOREFERENCES ||
+        entry.kind() == protos::pbzero::HeapGraphType::KIND_ARRAY ||
+        entry.kind() == protos::pbzero::HeapGraphType::KIND_STRING;
 
     heap_graph_tracker->AddInternedType(
-        seq_id, entry.iid(), context_->storage->InternString(str_view));
+        seq_id, entry.id(), context_->storage->InternString(str_view),
+        entry.location_id(), entry.object_size(), std::move(field_name_ids),
+        entry.superclass_id(), entry.classloader_id(), no_fields);
   }
   for (auto it = heap_graph.field_names(); it; ++it) {
     protos::pbzero::InternedString::Decoder entry(*it);
@@ -198,6 +215,7 @@
 
     HeapGraphTracker::SourceRoot src_root;
     src_root.root_type = context_->storage->InternString(str_view);
+    // grep-friendly: object_ids
     bool parse_error =
         ForEachVarInt<protos::pbzero::HeapGraphRoot::kObjectIdsFieldNumber>(
             entry, [&src_root](uint64_t value) {
@@ -225,23 +243,22 @@
                                       obfuscated_class_name_id);
 
   if (cls_objects) {
-    heap_graph_tracker->AddDeobfuscationMapping(
-        package_name_id, obfuscated_class_name_id,
-        context_->storage->InternString(
-            base::StringView(cls.deobfuscated_name())));
-
     for (tables::HeapGraphClassTable::Id id : *cls_objects) {
       uint32_t row =
           *context_->storage->heap_graph_class_table().id().IndexOf(id);
-      const StringPool::Id obfuscated_type_name =
+      const StringPool::Id obfuscated_type_name_id =
           context_->storage->heap_graph_class_table().name()[row];
-      StringPool::Id deobfuscated_type_name =
-          heap_graph_tracker->MaybeDeobfuscate(package_name_id,
-                                               obfuscated_type_name);
-      PERFETTO_CHECK(!deobfuscated_type_name.is_null());
+      const base::StringView obfuscated_type_name =
+          context_->storage->GetString(obfuscated_type_name_id);
+      NormalizedType normalized_type = GetNormalizedType(obfuscated_type_name);
+      std::string deobfuscated_type_name =
+          DenormalizeTypeName(normalized_type, cls.deobfuscated_name());
+      StringPool::Id deobfuscated_type_name_id =
+          context_->storage->InternString(
+              base::StringView(deobfuscated_type_name));
       context_->storage->mutable_heap_graph_class_table()
           ->mutable_deobfuscated_name()
-          ->Set(row, deobfuscated_type_name);
+          ->Set(row, deobfuscated_type_name_id);
     }
   } else {
     PERFETTO_DLOG("Class %s not found",
@@ -282,17 +299,8 @@
       std::string merged_obfuscated = cls.obfuscated_name().ToStdString() +
                                       "." +
                                       member.obfuscated_name().ToStdString();
-      std::string merged_deobfuscated;
-      std::string member_deobfuscated_name =
-          member.deobfuscated_name().ToStdString();
-      if (member_deobfuscated_name.find('.') == std::string::npos) {
-        // Name relative to class.
-        merged_deobfuscated = cls.deobfuscated_name().ToStdString() + "." +
-                              member_deobfuscated_name;
-      } else {
-        // Fully qualified name.
-        merged_deobfuscated = std::move(member_deobfuscated_name);
-      }
+      std::string merged_deobfuscated =
+          FullyQualifiedDeobfuscatedName(cls, member);
 
       auto obfuscated_field_name_id = context_->storage->string_pool().GetId(
           base::StringView(merged_obfuscated));
diff --git a/src/trace_processor/importers/proto/heap_graph_module.h b/src/trace_processor/importers/proto/heap_graph_module.h
index b1816b0..76f4146 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.h
+++ b/src/trace_processor/importers/proto/heap_graph_module.h
@@ -22,6 +22,7 @@
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index e0fe640..3a12c21 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -18,42 +18,27 @@
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
+#include "src/trace_processor/tables/profiler_tables.h"
 
 #include <set>
+#include <utility>
 
 namespace perfetto {
 namespace trace_processor {
 
 namespace {
-base::Optional<base::StringView> PackageFromApp(base::StringView location) {
-  location = location.substr(base::StringView("/data/app/").size());
-  size_t slash = location.find('/');
-  if (slash == std::string::npos) {
-    return base::nullopt;
-  }
-  size_t second_slash = location.find('/', slash + 1);
-  if (second_slash == std::string::npos) {
-    location = location.substr(0, slash);
-  } else {
-    location = location.substr(slash + 1, second_slash - slash);
-  }
-  size_t minus = location.find('-');
-  if (minus == std::string::npos) {
-    return base::nullopt;
-  }
-  return location.substr(0, minus);
-}
 
-std::set<tables::HeapGraphObjectTable::Id> GetChildren(
-    const TraceStorage& storage,
-    tables::HeapGraphObjectTable::Id id) {
+template <typename F>
+void ForReferenceSet(const TraceStorage& storage,
+                     tables::HeapGraphObjectTable::Id id,
+                     F fn) {
   uint32_t row = *storage.heap_graph_object_table().id().IndexOf(id);
   base::Optional<uint32_t> reference_set_id =
       storage.heap_graph_object_table().reference_set_id()[row];
   if (!reference_set_id)
-    return {};
+    return;
   uint32_t cur_reference_set_id;
-  std::set<tables::HeapGraphObjectTable::Id> children;
   for (uint32_t reference_row = *reference_set_id;
        reference_row < storage.heap_graph_reference_table().row_count();
        ++reference_row) {
@@ -61,12 +46,24 @@
         storage.heap_graph_reference_table().reference_set_id()[reference_row];
     if (cur_reference_set_id != *reference_set_id)
       break;
-
-    PERFETTO_CHECK(
-        storage.heap_graph_reference_table().owner_id()[reference_row] == id);
-    children.emplace(
-        storage.heap_graph_reference_table().owned_id()[reference_row]);
+    if (!fn(reference_row))
+      break;
   }
+}
+
+std::set<tables::HeapGraphObjectTable::Id> GetChildren(
+    const TraceStorage& storage,
+    tables::HeapGraphObjectTable::Id id) {
+  std::set<tables::HeapGraphObjectTable::Id> children;
+  ForReferenceSet(
+      storage, id, [&storage, &children, id](uint32_t reference_row) {
+        PERFETTO_CHECK(
+            storage.heap_graph_reference_table().owner_id()[reference_row] ==
+            id);
+        children.emplace(
+            storage.heap_graph_reference_table().owned_id()[reference_row]);
+        return true;
+      });
   return children;
 }
 
@@ -156,6 +153,7 @@
   }
   return superclass_map;
 }
+
 }  // namespace
 
 void MarkRoot(TraceStorage* storage,
@@ -250,94 +248,6 @@
 HeapGraphTracker::HeapGraphTracker(TraceProcessorContext* context)
     : context_(context) {}
 
-base::Optional<std::string> HeapGraphTracker::PackageFromLocation(
-    base::StringView location) {
-  // List of some hardcoded apps that do not follow the scheme used in
-  // PackageFromApp. Ask for yours to be added.
-  //
-  // TODO(b/153632336): Get rid of the hardcoded list of system apps.
-  base::StringView sysui(
-      "/system_ext/priv-app/SystemUIGoogle/SystemUIGoogle.apk");
-  if (location.size() >= sysui.size() &&
-      location.substr(0, sysui.size()) == sysui) {
-    return "com.android.systemui";
-  }
-
-  base::StringView phonesky("/product/priv-app/Phonesky/Phonesky.apk");
-  if (location.size() >= phonesky.size() &&
-      location.substr(0, phonesky.size()) == phonesky) {
-    return "com.android.vending";
-  }
-
-  base::StringView maps("/product/app/Maps/Maps.apk");
-  if (location.size() >= maps.size() &&
-      location.substr(0, maps.size()) == maps) {
-    return "com.google.android.apps.maps";
-  }
-
-  base::StringView launcher(
-      "/system_ext/priv-app/NexusLauncherRelease/NexusLauncherRelease.apk");
-  if (location.size() >= launcher.size() &&
-      location.substr(0, launcher.size()) == launcher) {
-    return "com.google.android.apps.nexuslauncher";
-  }
-
-  base::StringView photos("/product/app/Photos/Photos.apk");
-  if (location.size() >= photos.size() &&
-      location.substr(0, photos.size()) == photos) {
-    return "com.google.android.apps.photos";
-  }
-
-  base::StringView wellbeing(
-      "/product/priv-app/WellbeingPrebuilt/WellbeingPrebuilt.apk");
-  if (location.size() >= wellbeing.size() &&
-      location.substr(0, wellbeing.size()) == wellbeing) {
-    return "com.google.android.apps.wellbeing";
-  }
-
-  base::StringView matchmaker("MatchMaker");
-  if (location.size() >= matchmaker.size() &&
-      location.find(matchmaker) != base::StringView::npos) {
-    return "com.google.android.as";
-  }
-
-  base::StringView gm("/product/app/PrebuiltGmail/PrebuiltGmail.apk");
-  if (location.size() >= gm.size() && location.substr(0, gm.size()) == gm) {
-    return "com.google.android.gm";
-  }
-
-  base::StringView gmscore("/product/priv-app/PrebuiltGmsCore/PrebuiltGmsCore");
-  if (location.size() >= gmscore.size() &&
-      location.substr(0, gmscore.size()) == gmscore) {
-    return "com.google.android.gms";
-  }
-
-  base::StringView velvet("/product/priv-app/Velvet/Velvet.apk");
-  if (location.size() >= velvet.size() &&
-      location.substr(0, velvet.size()) == velvet) {
-    return "com.google.android.googlequicksearchbox";
-  }
-
-  base::StringView inputmethod(
-      "/product/app/LatinIMEGooglePrebuilt/LatinIMEGooglePrebuilt.apk");
-  if (location.size() >= inputmethod.size() &&
-      location.substr(0, inputmethod.size()) == inputmethod) {
-    return "com.google.android.inputmethod.latin";
-  }
-
-  base::StringView data_app("/data/app/");
-  if (location.substr(0, data_app.size()) == data_app) {
-    auto package = PackageFromApp(location);
-    if (!package) {
-      PERFETTO_DLOG("Failed to parse %s", location.ToStdString().c_str());
-      context_->storage->IncrementStats(stats::heap_graph_location_parse_error);
-      return base::nullopt;
-    }
-    return package->ToStdString();
-  }
-  return base::nullopt;
-}
-
 HeapGraphTracker::SequenceState& HeapGraphTracker::GetOrCreateSequence(
     uint32_t seq_id) {
   return sequence_state_[seq_id];
@@ -417,15 +327,20 @@
   hgo->mutable_self_size()->Set(row, static_cast<int64_t>(obj.self_size));
   hgo->mutable_type_id()->Set(row, type_id);
 
+  if (obj.self_size == 0)
+    sequence_state.deferred_size_objects_for_type_[type_id].push_back(owner_id);
+
   uint32_t reference_set_id =
       context_->storage->heap_graph_reference_table().row_count();
   bool any_references = false;
-  for (const SourceObject::Reference& ref : obj.references) {
+
+  for (size_t i = 0; i < obj.referred_objects.size(); ++i) {
+    uint64_t owned_object_id = obj.referred_objects[i];
     // This is true for unset reference fields.
-    if (ref.owned_object_id == 0)
+    if (owned_object_id == 0)
       continue;
     tables::HeapGraphObjectTable::Id owned_id =
-        GetOrInsertObject(&sequence_state, ref.owned_object_id);
+        GetOrInsertObject(&sequence_state, owned_object_id);
 
     auto ref_id_and_row =
         context_->storage->mutable_heap_graph_reference_table()->Insert(
@@ -435,8 +350,10 @@
              {},
              {},
              /*deobfuscated_field_name=*/base::nullopt});
-    sequence_state.references_for_field_name_id[ref.field_name_id].push_back(
-        ref_id_and_row.id);
+    if (!obj.field_name_ids.empty()) {
+      sequence_state.references_for_field_name_id[obj.field_name_ids[i]]
+          .push_back(ref_id_and_row.id);
+    }
     any_references = true;
   }
   if (any_references) {
@@ -445,6 +362,10 @@
     context_->storage->mutable_heap_graph_object_table()
         ->mutable_reference_set_id()
         ->Set(owner_row, reference_set_id);
+    if (obj.field_name_ids.empty()) {
+      sequence_state.deferred_reference_objects_for_type_[type_id].push_back(
+          owner_id);
+    }
   }
 }
 
@@ -469,10 +390,21 @@
 void HeapGraphTracker::AddInternedType(uint32_t seq_id,
                                        uint64_t intern_id,
                                        StringPool::Id strid,
-                                       base::Optional<uint64_t> location_id) {
+                                       uint64_t location_id,
+                                       uint64_t object_size,
+                                       std::vector<uint64_t> field_name_ids,
+                                       uint64_t superclass_id,
+                                       uint64_t classloader_id,
+                                       bool no_fields) {
   SequenceState& sequence_state = GetOrCreateSequence(seq_id);
   sequence_state.interned_types[intern_id].name = strid;
   sequence_state.interned_types[intern_id].location_id = location_id;
+  sequence_state.interned_types[intern_id].object_size = object_size;
+  sequence_state.interned_types[intern_id].field_name_ids =
+      std::move(field_name_ids);
+  sequence_state.interned_types[intern_id].superclass_id = superclass_id;
+  sequence_state.interned_types[intern_id].classloader_id = classloader_id;
+  sequence_state.interned_types[intern_id].no_fields = no_fields;
 }
 
 void HeapGraphTracker::AddInternedFieldName(uint32_t seq_id,
@@ -487,6 +419,10 @@
   }
   StringPool::Id field_name = context_->storage->InternString(str);
   StringPool::Id type_name = context_->storage->InternString(type);
+
+  sequence_state.interned_fields.emplace(intern_id,
+                                         InternedField{field_name, type_name});
+
   auto it = sequence_state.references_for_field_name_id.find(intern_id);
   if (it != sequence_state.references_for_field_name_id.end()) {
     auto hgr = context_->storage->mutable_heap_graph_reference_table();
@@ -513,6 +449,7 @@
   }
 
   if (dropped_packet) {
+    sequence_state.truncated = true;
     if (sequence_state.prev_index) {
       PERFETTO_ELOG("Missing packets between %" PRIu64 " and %" PRIu64,
                     *sequence_state.prev_index, index);
@@ -527,8 +464,28 @@
   sequence_state.prev_index = index;
 }
 
+// This only works on Android S+ traces. We need to have ingested the whole
+// profile before calling this function (e.g. in FinalizeProfile).
+HeapGraphTracker::InternedType* HeapGraphTracker::GetSuperClass(
+    SequenceState* sequence_state,
+    const InternedType* current_type) {
+  if (current_type->superclass_id) {
+    auto it = sequence_state->interned_types.find(current_type->superclass_id);
+    if (it != sequence_state->interned_types.end())
+      return &it->second;
+  }
+  context_->storage->IncrementIndexedStats(
+      stats::heap_graph_malformed_packet,
+      static_cast<int>(sequence_state->current_upid));
+  return nullptr;
+}
+
 void HeapGraphTracker::FinalizeProfile(uint32_t seq_id) {
   SequenceState& sequence_state = GetOrCreateSequence(seq_id);
+  if (sequence_state.truncated) {
+    truncated_graphs_.emplace(
+        std::make_pair(sequence_state.current_upid, sequence_state.current_ts));
+  }
 
   // We do this in FinalizeProfile because the interned_location_names get
   // written at the end of the dump.
@@ -550,9 +507,74 @@
     tables::HeapGraphClassTable::Id type_id =
         GetOrInsertType(&sequence_state, id);
 
+    auto sz_obj_it =
+        sequence_state.deferred_size_objects_for_type_.find(type_id);
+    if (sz_obj_it != sequence_state.deferred_size_objects_for_type_.end()) {
+      for (tables::HeapGraphObjectTable::Id obj_id : sz_obj_it->second) {
+        auto* hgo = context_->storage->mutable_heap_graph_object_table();
+        uint32_t row = *hgo->id().IndexOf(obj_id);
+        hgo->mutable_self_size()->Set(
+            row, static_cast<int64_t>(interned_type.object_size));
+      }
+      sequence_state.deferred_size_objects_for_type_.erase(sz_obj_it);
+    }
+
+    auto ref_obj_it =
+        sequence_state.deferred_reference_objects_for_type_.find(type_id);
+    if (ref_obj_it !=
+        sequence_state.deferred_reference_objects_for_type_.end()) {
+      for (tables::HeapGraphObjectTable::Id obj_id : ref_obj_it->second) {
+        const InternedType* current_type = &interned_type;
+        if (interned_type.no_fields) {
+          continue;
+        }
+        size_t field_offset_in_cls = 0;
+        ForReferenceSet(
+            *context_->storage, obj_id,
+            [this, &current_type, &sequence_state,
+             &field_offset_in_cls](uint32_t reference_row) {
+              while (current_type && field_offset_in_cls >=
+                                         current_type->field_name_ids.size()) {
+                size_t prev_type_size = current_type->field_name_ids.size();
+                current_type = GetSuperClass(&sequence_state, current_type);
+                field_offset_in_cls -= prev_type_size;
+              }
+
+              if (!current_type) {
+                return false;
+              }
+
+              uint64_t field_id =
+                  current_type->field_name_ids[field_offset_in_cls++];
+              auto it = sequence_state.interned_fields.find(field_id);
+              if (it == sequence_state.interned_fields.end()) {
+                PERFETTO_ELOG("Invalid field id.");
+                context_->storage->IncrementIndexedStats(
+                    stats::heap_graph_malformed_packet,
+                    static_cast<int>(sequence_state.current_upid));
+                return true;
+              }
+              const InternedField& field = it->second;
+              auto hgr =
+                  context_->storage->mutable_heap_graph_reference_table();
+              hgr->mutable_field_name()->Set(reference_row, field.name);
+              hgr->mutable_field_type_name()->Set(reference_row,
+                                                  field.type_name);
+              field_to_rows_[field.name].emplace_back(reference_row);
+              return true;
+            });
+      }
+      sequence_state.deferred_reference_objects_for_type_.erase(ref_obj_it);
+    }
+
     auto* hgc = context_->storage->mutable_heap_graph_class_table();
     uint32_t row = *hgc->id().IndexOf(type_id);
     hgc->mutable_name()->Set(row, interned_type.name);
+    if (interned_type.classloader_id) {
+      auto classloader_object_id =
+          GetOrInsertObject(&sequence_state, interned_type.classloader_id);
+      hgc->mutable_classloader_id()->Set(row, classloader_object_id.value);
+    }
     if (location_name)
       hgc->mutable_location()->Set(row, *location_name);
 
@@ -570,7 +592,8 @@
 
     if (location_name && !is_base_apk) {
       base::Optional<std::string> package_name =
-          PackageFromLocation(context_->storage->GetString(*location_name));
+          PackageFromLocation(context_->storage.get(),
+                              context_->storage->GetString(*location_name));
       if (package_name) {
         class_to_rows_[std::make_pair(
                            context_->storage->InternString(
@@ -593,6 +616,18 @@
     }
   }
 
+  if (!sequence_state.deferred_size_objects_for_type_.empty()) {
+    context_->storage->IncrementIndexedStats(
+        stats::heap_graph_malformed_packet,
+        static_cast<int>(sequence_state.current_upid));
+  }
+
+  if (!sequence_state.deferred_reference_objects_for_type_.empty()) {
+    context_->storage->IncrementIndexedStats(
+        stats::heap_graph_malformed_packet,
+        static_cast<int>(sequence_state.current_upid));
+  }
+
   for (const SourceRoot& root : sequence_state.current_roots) {
     for (uint64_t obj_id : root.object_ids) {
       auto it = sequence_state.object_id_to_db_id.find(obj_id);
@@ -614,6 +649,7 @@
   sequence_state_.erase(seq_id);
 }
 
+// TODO(fmayer): For Android S+ traces, use the superclass_id from the trace.
 void HeapGraphTracker::PopulateSuperClasses(const SequenceState& seq) {
   // Maps from normalized class name and location, to superclass.
   std::map<ClassDescriptor, ClassDescriptor> superclass_map =
@@ -686,13 +722,22 @@
 
     tables::HeapGraphClassTable::Id type_id =
         storage.heap_graph_object_table().type_id()[row];
-    auto it = path->nodes[parent_id].children.find(type_id);
+
+    uint32_t type_row = *storage.heap_graph_class_table().id().IndexOf(type_id);
+    base::Optional<StringPool::Id> opt_class_name_id =
+        storage.heap_graph_class_table().deobfuscated_name()[type_row];
+    if (!opt_class_name_id) {
+      opt_class_name_id = storage.heap_graph_class_table().name()[type_row];
+    }
+    PERFETTO_CHECK(opt_class_name_id);
+    StringPool::Id class_name_id = *opt_class_name_id;
+    auto it = path->nodes[parent_id].children.find(class_name_id);
     if (it == path->nodes[parent_id].children.end()) {
       size_t path_id = path->nodes.size();
       path->nodes.emplace_back(PathFromRoot::Node{});
       std::tie(it, std::ignore) =
-          path->nodes[parent_id].children.emplace(type_id, path_id);
-      path->nodes.back().type_id = type_id;
+          path->nodes[parent_id].children.emplace(class_name_id, path_id);
+      path->nodes.back().class_name_id = class_name_id;
       path->nodes.back().depth = depth;
       path->nodes.back().parent_id = parent_id;
     }
@@ -742,22 +787,44 @@
 std::unique_ptr<tables::ExperimentalFlamegraphNodesTable>
 HeapGraphTracker::BuildFlamegraph(const int64_t current_ts,
                                   const UniquePid current_upid) {
-  auto it = roots_.find(std::make_pair(current_upid, current_ts));
-  if (it == roots_.end())
-    return nullptr;
-
-  const std::set<tables::HeapGraphObjectTable::Id>& roots = it->second;
+  auto profile_type = context_->storage->InternString("graph");
+  auto java_mapping = context_->storage->InternString("JAVA");
 
   std::unique_ptr<tables::ExperimentalFlamegraphNodesTable> tbl(
       new tables::ExperimentalFlamegraphNodesTable(
           context_->storage->mutable_string_pool(), nullptr));
 
+  auto it = roots_.find(std::make_pair(current_upid, current_ts));
+  if (it == roots_.end()) {
+    // TODO(fmayer): This should not be within the flame graph but some marker
+    // in the UI.
+    if (IsTruncated(current_upid, current_ts)) {
+      tables::ExperimentalFlamegraphNodesTable::Row alloc_row{};
+      alloc_row.ts = current_ts;
+      alloc_row.upid = current_upid;
+      alloc_row.profile_type = profile_type;
+      alloc_row.depth = 0;
+      alloc_row.name =
+          context_->storage->InternString("ERROR: INCOMPLETE GRAPH");
+      alloc_row.map_name = java_mapping;
+      alloc_row.count = 1;
+      alloc_row.cumulative_count = 1;
+      alloc_row.size = 1;
+      alloc_row.cumulative_size = 1;
+      alloc_row.parent_id = base::nullopt;
+      tbl->Insert(alloc_row);
+      return tbl;
+    }
+    // We haven't seen this graph, so we should raise an error.
+    return nullptr;
+  }
+
+  const std::set<tables::HeapGraphObjectTable::Id>& roots = it->second;
+
   PathFromRoot init_path;
   for (tables::HeapGraphObjectTable::Id root : roots) {
     FindPathFromRoot(*context_->storage, root, &init_path);
   }
-  auto profile_type = context_->storage->InternString("graph");
-  auto java_mapping = context_->storage->InternString("JAVA");
 
   std::vector<int32_t> node_to_cumulative_size(init_path.nodes.size());
   std::vector<int32_t> node_to_cumulative_count(init_path.nodes.size());
@@ -781,22 +848,12 @@
       parent_id = node_to_id[node.parent_id];
     const uint32_t depth = node.depth;
 
-    uint32_t type_row =
-        *context_->storage->heap_graph_class_table().id().IndexOf(node.type_id);
-    base::Optional<StringPool::Id> name =
-        context_->storage->heap_graph_class_table()
-            .deobfuscated_name()[type_row];
-    if (!name) {
-      name = context_->storage->heap_graph_class_table().name()[type_row];
-    }
-    PERFETTO_CHECK(name);
-
     tables::ExperimentalFlamegraphNodesTable::Row alloc_row{};
     alloc_row.ts = current_ts;
     alloc_row.upid = current_upid;
     alloc_row.profile_type = profile_type;
     alloc_row.depth = depth;
-    alloc_row.name = *name;
+    alloc_row.name = node.class_name_id;
     alloc_row.map_name = java_mapping;
     alloc_row.count = static_cast<int64_t>(node.count);
     alloc_row.cumulative_count =
@@ -820,29 +877,22 @@
   }
 }
 
-StringPool::Id HeapGraphTracker::MaybeDeobfuscate(
-    base::Optional<StringPool::Id> package_name,
-    StringPool::Id id) {
-  base::StringView type_name = context_->storage->GetString(id);
-  auto normalized_type = GetNormalizedType(type_name);
-  auto it = deobfuscation_mapping_.find(std::make_pair(
-      package_name, context_->storage->InternString(normalized_type.name)));
-  if (it == deobfuscation_mapping_.end())
-    return id;
+bool HeapGraphTracker::IsTruncated(UniquePid upid, int64_t ts) {
+  // The graph was finalized but was missing packets.
+  if (truncated_graphs_.find(std::make_pair(upid, ts)) !=
+      truncated_graphs_.end()) {
+    return true;
+  }
 
-  base::StringView normalized_deobfuscated_name =
-      context_->storage->GetString(it->second);
-  std::string result =
-      DenormalizeTypeName(normalized_type, normalized_deobfuscated_name);
-  return context_->storage->InternString(base::StringView(result));
-}
-
-void HeapGraphTracker::AddDeobfuscationMapping(
-    base::Optional<StringPool::Id> package_name,
-    StringPool::Id obfuscated_name,
-    StringPool::Id deobfuscated_name) {
-  deobfuscation_mapping_.emplace(std::make_pair(package_name, obfuscated_name),
-                                 deobfuscated_name);
+  // Or the graph was never finalized, so is missing packets at the end.
+  for (const auto& p : sequence_state_) {
+    const SequenceState& sequence_state = p.second;
+    if (sequence_state.current_upid == upid &&
+        sequence_state.current_ts == ts) {
+      return true;
+    }
+  }
+  return false;
 }
 
 HeapGraphTracker::~HeapGraphTracker() = default;
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.h b/src/trace_processor/importers/proto/heap_graph_tracker.h
index 08c75ca..8b27242 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.h
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.h
@@ -19,6 +19,7 @@
 
 #include <map>
 #include <set>
+#include <utility>
 #include <vector>
 
 #include "perfetto/ext/base/optional.h"
@@ -47,8 +48,8 @@
     size_t parent_id = 0;
     int64_t size = 0;
     int64_t count = 0;
-    tables::HeapGraphClassTable::Id type_id = {};
-    std::map<tables::HeapGraphClassTable::Id, size_t> children;
+    StringId class_name_id = {};
+    std::map<StringId, size_t> children;
   };
   std::vector<Node> nodes{Node{}};
   std::set<tables::HeapGraphObjectTable::Id> visited;
@@ -61,7 +62,6 @@
                       tables::HeapGraphObjectTable::Id id,
                       PathFromRoot* path);
 
-base::Optional<std::string> PackageFromLocation(base::StringView location);
 base::Optional<base::StringView> GetStaticClassTypeName(base::StringView type);
 size_t NumberOfArrays(base::StringView type);
 NormalizedType GetNormalizedType(base::StringView type);
@@ -74,14 +74,12 @@
   struct SourceObject {
     // All ids in this are in the trace iid space, not in the trace processor
     // id space.
-    struct Reference {
-      uint64_t field_name_id = 0;
-      uint64_t owned_object_id = 0;
-    };
     uint64_t object_id = 0;
     uint64_t self_size = 0;
     uint64_t type_id = 0;
-    std::vector<Reference> references;
+
+    std::vector<uint64_t> field_name_ids;
+    std::vector<uint64_t> referred_objects;
   };
 
   struct SourceRoot {
@@ -103,7 +101,12 @@
   void AddInternedType(uint32_t seq_id,
                        uint64_t intern_id,
                        StringPool::Id strid,
-                       base::Optional<uint64_t> location_id = {});
+                       uint64_t location_id,
+                       uint64_t object_size,
+                       std::vector<uint64_t> field_name_ids,
+                       uint64_t superclass_id,
+                       uint64_t classloader_id,
+                       bool no_fields);
   void AddInternedFieldName(uint32_t seq_id,
                             uint64_t intern_id,
                             base::StringView str);
@@ -116,12 +119,6 @@
   ~HeapGraphTracker() override;
   void NotifyEndOfFile();
 
-  void AddDeobfuscationMapping(base::Optional<StringPool::Id> package_name,
-                               StringPool::Id obfuscated_name,
-                               StringPool::Id deobfuscated_name);
-  StringPool::Id MaybeDeobfuscate(base::Optional<StringPool::Id> package_name,
-                                  StringPool::Id);
-
   const std::vector<tables::HeapGraphClassTable::Id>* RowsForType(
       base::Optional<StringPool::Id> package_name,
       StringPool::Id type_name) const {
@@ -142,9 +139,6 @@
       const int64_t current_ts,
       const UniquePid current_upid);
 
-  // public for testing.
-  base::Optional<std::string> PackageFromLocation(base::StringView location);
-
  private:
   struct InternedField {
     StringPool::Id name;
@@ -153,6 +147,11 @@
   struct InternedType {
     StringPool::Id name;
     base::Optional<uint64_t> location_id;
+    uint64_t object_size;
+    std::vector<uint64_t> field_name_ids;
+    uint64_t superclass_id;
+    bool no_fields;
+    uint64_t classloader_id;
   };
   struct SequenceState {
     UniquePid current_upid = 0;
@@ -164,7 +163,19 @@
     std::map<uint64_t, tables::HeapGraphClassTable::Id> type_id_to_db_id;
     std::map<uint64_t, std::vector<tables::HeapGraphReferenceTable::Id>>
         references_for_field_name_id;
+    std::map<uint64_t, InternedField> interned_fields;
+    std::map<tables::HeapGraphClassTable::Id,
+             std::vector<tables::HeapGraphObjectTable::Id>>
+        deferred_reference_objects_for_type_;
     base::Optional<uint64_t> prev_index;
+    // For most objects, we need not store the size in the object's message
+    // itself, because all instances of the type have the same type. In this
+    // case, we defer setting self_size in the table until we process the class
+    // message in FinalizeProfile.
+    std::map<tables::HeapGraphClassTable::Id,
+             std::vector<tables::HeapGraphObjectTable::Id>>
+        deferred_size_objects_for_type_;
+    bool truncated = false;
   };
 
   SequenceState& GetOrCreateSequence(uint32_t seq_id);
@@ -175,6 +186,9 @@
                                                   uint64_t type_id);
   bool SetPidAndTimestamp(SequenceState* seq, UniquePid upid, int64_t ts);
   void PopulateSuperClasses(const SequenceState& seq);
+  InternedType* GetSuperClass(SequenceState* sequence_state,
+                              const InternedType* current_type);
+  bool IsTruncated(UniquePid upid, int64_t ts);
 
   TraceProcessorContext* const context_;
   std::map<uint32_t, SequenceState> sequence_state_;
@@ -190,6 +204,7 @@
   std::map<std::pair<UniquePid, int64_t>,
            std::set<tables::HeapGraphObjectTable::Id>>
       roots_;
+  std::set<std::pair<UniquePid, int64_t>> truncated_graphs_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index 0bb38a3..1d6ad11 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -16,6 +16,8 @@
 
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
 
+#include "src/trace_processor/importers/proto/profiler_util.h"
+
 #include "perfetto/base/logging.h"
 #include "test/gtest_and_gmock.h"
 
@@ -26,17 +28,22 @@
 using ::testing::UnorderedElementsAre;
 
 TEST(HeapGraphTrackerTest, PackageFromLocationApp) {
-  TraceProcessorContext context;
-  context.storage.reset(new TraceStorage);
-  HeapGraphTracker tracker(&context);
-  EXPECT_EQ(tracker.PackageFromLocation(
-                "/data/app/~~ASDFGH1234QWerT==/"
-                "com.twitter.android-MNBVCX7890SDTst6==/test.apk"),
-            "com.twitter.android");
-  EXPECT_EQ(tracker.PackageFromLocation(
+  TraceStorage storage;
+  EXPECT_EQ(
+      PackageFromLocation(&storage,
+                          "/data/app/~~ASDFGH1234QWerT==/"
+                          "com.twitter.android-MNBVCX7890SDTst6==/test.apk"),
+      "com.twitter.android");
+  EXPECT_EQ(PackageFromLocation(
+                &storage,
                 "/data/app/com.google.android.webview-6XfQhnaSkFwGK0sYL9is0G==/"
                 "base.apk"),
             "com.google.android.webview");
+  EXPECT_EQ(PackageFromLocation(&storage,
+                                "/data/app/"
+                                "com.google.android.apps.wellbeing-"
+                                "qfQCaB4uJ7P0OPpZQqOu0Q==/oat/arm64/base.odex"),
+            "com.google.android.apps.wellbeing");
 }
 
 TEST(HeapGraphTrackerTest, BuildFlamegraph) {
@@ -56,6 +63,7 @@
   HeapGraphTracker tracker(&context);
 
   constexpr uint64_t kField = 1;
+  constexpr uint64_t kLocation = 0;
 
   constexpr uint64_t kX = 1;
   constexpr uint64_t kY = 2;
@@ -70,24 +78,28 @@
 
   tracker.AddInternedFieldName(kSeqId, kField, field);
 
-  tracker.AddInternedType(kSeqId, kX, x);
-  tracker.AddInternedType(kSeqId, kY, y);
-  tracker.AddInternedType(kSeqId, kA, a);
-  tracker.AddInternedType(kSeqId, kB, b);
+  tracker.AddInternedLocationName(kSeqId, kLocation,
+                                  context.storage->InternString("location"));
+  tracker.AddInternedType(kSeqId, kX, x, kLocation, /*object_size=*/0,
+                          /*field_name_ids=*/{}, /*superclass_id=*/0,
+                          /*classloader_id=*/0, /*no_fields=*/false);
+  tracker.AddInternedType(kSeqId, kY, y, kLocation, /*object_size=*/0,
+                          /*field_name_ids=*/{}, /*superclass_id=*/0,
+                          /*classloader_id=*/0, /*no_fields=*/false);
+  tracker.AddInternedType(kSeqId, kA, a, kLocation, /*object_size=*/0,
+                          /*field_name_ids=*/{}, /*superclass_id=*/0,
+                          /*classloader_id=*/0, /*no_fields=*/false);
+  tracker.AddInternedType(kSeqId, kB, b, kLocation, /*object_size=*/0,
+                          /*field_name_ids=*/{}, /*superclass_id=*/0,
+                          /*classloader_id=*/0, /*no_fields=*/false);
 
   {
     HeapGraphTracker::SourceObject obj;
     obj.object_id = 1;
     obj.self_size = 1;
     obj.type_id = kX;
-    HeapGraphTracker::SourceObject::Reference ref;
-    ref.field_name_id = kField;
-    ref.owned_object_id = 2;
-    obj.references.emplace_back(std::move(ref));
-
-    ref.field_name_id = kField;
-    ref.owned_object_id = 3;
-    obj.references.emplace_back(std::move(ref));
+    obj.field_name_ids = {kField, kField};
+    obj.referred_objects = {2, 3};
 
     tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
   }
@@ -105,14 +117,8 @@
     obj.object_id = 3;
     obj.self_size = 3;
     obj.type_id = kY;
-    HeapGraphTracker::SourceObject::Reference ref;
-    ref.field_name_id = kField;
-    ref.owned_object_id = 4;
-    obj.references.emplace_back(std::move(ref));
-
-    ref.field_name_id = kField;
-    ref.owned_object_id = 5;
-    obj.references.emplace_back(std::move(ref));
+    obj.field_name_ids = {kField, kField};
+    obj.referred_objects = {4, 5};
 
     tracker.AddObject(kSeqId, kPid, kTimestamp, std::move(obj));
   }
diff --git a/src/trace_processor/importers/proto/heap_profile_tracker.cc b/src/trace_processor/importers/proto/heap_profile_tracker.cc
index 602a6bd..20e9a42 100644
--- a/src/trace_processor/importers/proto/heap_profile_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_profile_tracker.cc
@@ -59,7 +59,10 @@
 
   if (!symbol_set_id) {
     StringId frame_name = frames_tbl.name()[frame_idx];
-    return {{frame_name, mapping_name, base::nullopt}};
+    base::Optional<StringId> deobfuscated_name =
+        frames_tbl.deobfuscated_name()[frame_idx];
+    return {{deobfuscated_name ? *deobfuscated_name : frame_name, mapping_name,
+             base::nullopt}};
   }
 
   std::vector<MergedCallsite> result;
@@ -106,11 +109,14 @@
     auto opt_parent_id = callsites_tbl.parent_id()[i];
     if (opt_parent_id) {
       parent_idx = callsites_tbl.id().IndexOf(*opt_parent_id);
-      parent_idx = callsite_to_merged_callsite[*parent_idx];
+      // Make sure what we index into has been populated already.
       PERFETTO_CHECK(*parent_idx < i);
+      parent_idx = callsite_to_merged_callsite[*parent_idx];
     }
 
     auto callsites = GetMergedCallsites(storage, i);
+    // Loop below needs to run at least once for parent_idx to get updated.
+    PERFETTO_CHECK(!callsites.empty());
     for (MergedCallsite& merged_callsite : callsites) {
       merged_callsite.parent_idx = parent_idx;
       auto it = merged_callsites_to_table_idx.find(merged_callsite);
@@ -137,6 +143,7 @@
       }
       parent_idx = it->second;
     }
+
     PERFETTO_CHECK(parent_idx);
     callsite_to_merged_callsite[i] = *parent_idx;
   }
@@ -251,12 +258,12 @@
 
 void HeapProfileTracker::AddAllocation(
     uint32_t seq_id,
-    StackProfileTracker* stack_profile_tracker,
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
     const SourceAllocation& alloc,
-    const StackProfileTracker::InternLookup* intern_lookup) {
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
   SequenceState& sequence_state = sequence_state_[seq_id];
 
-  auto opt_callstack_id = stack_profile_tracker->FindOrInsertCallstack(
+  auto opt_callstack_id = sequence_stack_profile_tracker->FindOrInsertCallstack(
       alloc.callstack_id, intern_lookup);
   if (!opt_callstack_id)
     return;
@@ -345,11 +352,12 @@
     return;
   }
 
-  if (alloc_delta.count) {
+  // Dump at max profiles do not have .count set.
+  if (alloc_delta.count || alloc_delta.size) {
     context_->storage->mutable_heap_profile_allocation_table()->Insert(
         alloc_delta);
   }
-  if (free_delta.count) {
+  if (free_delta.count || free_delta.size) {
     context_->storage->mutable_heap_profile_allocation_table()->Insert(
         free_delta);
   }
@@ -366,20 +374,20 @@
 
 void HeapProfileTracker::CommitAllocations(
     uint32_t seq_id,
-    StackProfileTracker* stack_profile_tracker,
-    const StackProfileTracker::InternLookup* intern_lookup) {
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
   SequenceState& sequence_state = sequence_state_[seq_id];
   for (const auto& p : sequence_state.pending_allocs)
-    AddAllocation(seq_id, stack_profile_tracker, p, intern_lookup);
+    AddAllocation(seq_id, sequence_stack_profile_tracker, p, intern_lookup);
   sequence_state.pending_allocs.clear();
 }
 
 void HeapProfileTracker::FinalizeProfile(
     uint32_t seq_id,
-    StackProfileTracker* stack_profile_tracker,
-    const StackProfileTracker::InternLookup* intern_lookup) {
-  CommitAllocations(seq_id, stack_profile_tracker, intern_lookup);
-  stack_profile_tracker->ClearIndices();
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
+  CommitAllocations(seq_id, sequence_stack_profile_tracker, intern_lookup);
+  sequence_stack_profile_tracker->ClearIndices();
 }
 
 void HeapProfileTracker::NotifyEndOfFile() {
diff --git a/src/trace_processor/importers/proto/heap_profile_tracker.h b/src/trace_processor/importers/proto/heap_profile_tracker.h
index 0f6e174..066c238 100644
--- a/src/trace_processor/importers/proto/heap_profile_tracker.h
+++ b/src/trace_processor/importers/proto/heap_profile_tracker.h
@@ -40,7 +40,7 @@
     // converts this for us.
     int64_t timestamp = 0;
     StringPool::Id heap_name;
-    StackProfileTracker::SourceCallstackId callstack_id = 0;
+    SequenceStackProfileTracker::SourceCallstackId callstack_id = 0;
     uint64_t self_allocated = 0;
     uint64_t self_freed = 0;
     uint64_t alloc_count = 0;
@@ -56,15 +56,17 @@
   // Call after the last profile packet of a dump to commit the allocations
   // that had been stored using StoreAllocation and clear internal indices
   // for that dump.
-  void FinalizeProfile(uint32_t seq_id,
-                       StackProfileTracker* stack_profile_tracker,
-                       const StackProfileTracker::InternLookup* lookup);
+  void FinalizeProfile(
+      uint32_t seq_id,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
+      const SequenceStackProfileTracker::InternLookup* lookup);
 
   // Only commit the allocations that had been stored using StoreAllocations.
   // This is only needed in tests, use FinalizeProfile instead.
-  void CommitAllocations(uint32_t seq_id,
-                         StackProfileTracker* stack_profile_tracker,
-                         const StackProfileTracker::InternLookup* lookup);
+  void CommitAllocations(
+      uint32_t seq_id,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
+      const SequenceStackProfileTracker::InternLookup* lookup);
 
   void NotifyEndOfFile();
 
@@ -73,12 +75,12 @@
  private:
   void AddAllocation(
       uint32_t seq_id,
-      StackProfileTracker* stack_profile_tracker,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
       const SourceAllocation&,
-      const StackProfileTracker::InternLookup* intern_lookup = nullptr);
+      const SequenceStackProfileTracker::InternLookup* intern_lookup = nullptr);
   struct SourceAllocationIndex {
     UniquePid upid;
-    StackProfileTracker::SourceCallstackId src_callstack_id;
+    SequenceStackProfileTracker::SourceCallstackId src_callstack_id;
     StringPool::Id heap_name;
     bool operator<(const SourceAllocationIndex& o) const {
       return std::tie(upid, src_callstack_id, heap_name) <
@@ -106,10 +108,10 @@
     // SourceCallstackId for a CallsiteId, we put the previous value into
     // the correction maps below.
     std::map<SourceAllocationIndex, std::set<CallsiteId>> seen_callstacks;
-    std::map<StackProfileTracker::SourceCallstackId,
+    std::map<SequenceStackProfileTracker::SourceCallstackId,
              tables::HeapProfileAllocationTable::Row>
         alloc_correction;
-    std::map<StackProfileTracker::SourceCallstackId,
+    std::map<SequenceStackProfileTracker::SourceCallstackId,
              tables::HeapProfileAllocationTable::Row>
         free_correction;
 
diff --git a/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
index 1a4e493..0ac2608 100644
--- a/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
@@ -56,7 +56,9 @@
  public:
   HeapProfileTrackerDupTest() {
     context.storage.reset(new TraceStorage());
-    stack_profile_tracker.reset(new StackProfileTracker(&context));
+    context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
+    sequence_stack_profile_tracker.reset(
+        new SequenceStackProfileTracker(&context));
     context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
 
     mapping_name = context.storage->InternString("[mapping]");
@@ -67,11 +69,12 @@
 
  protected:
   void InsertMapping(const Packet& packet) {
-    stack_profile_tracker->AddString(packet.mapping_name_id, "[mapping]");
+    sequence_stack_profile_tracker->AddString(packet.mapping_name_id,
+                                              "[mapping]");
 
-    stack_profile_tracker->AddString(packet.build_id, kBuildIDName);
+    sequence_stack_profile_tracker->AddString(packet.build_id, kBuildIDName);
 
-    StackProfileTracker::SourceMapping first_frame;
+    SequenceStackProfileTracker::SourceMapping first_frame;
     first_frame.build_id = packet.build_id;
     first_frame.exact_offset = kMappingExactOffset;
     first_frame.start_offset = kMappingStartOffset;
@@ -80,27 +83,27 @@
     first_frame.load_bias = kMappingLoadBias;
     first_frame.name_ids = {packet.mapping_name_id};
 
-    stack_profile_tracker->AddMapping(packet.mapping_id, first_frame);
+    sequence_stack_profile_tracker->AddMapping(packet.mapping_id, first_frame);
   }
 
   void InsertFrame(const Packet& packet) {
     InsertMapping(packet);
-    stack_profile_tracker->AddString(packet.frame_name_id, "[frame]");
+    sequence_stack_profile_tracker->AddString(packet.frame_name_id, "[frame]");
 
-    StackProfileTracker::SourceFrame first_frame;
+    SequenceStackProfileTracker::SourceFrame first_frame;
     first_frame.name_id = packet.frame_name_id;
     first_frame.mapping_id = packet.mapping_id;
     first_frame.rel_pc = kFrameRelPc;
 
-    stack_profile_tracker->AddFrame(packet.frame_id, first_frame);
+    sequence_stack_profile_tracker->AddFrame(packet.frame_id, first_frame);
   }
 
   void InsertCallsite(const Packet& packet) {
     InsertFrame(packet);
 
-    StackProfileTracker::SourceCallstack first_callsite = {packet.frame_id,
-                                                           packet.frame_id};
-    stack_profile_tracker->AddCallstack(kCallstackId, first_callsite);
+    SequenceStackProfileTracker::SourceCallstack first_callsite = {
+        packet.frame_id, packet.frame_id};
+    sequence_stack_profile_tracker->AddCallstack(kCallstackId, first_callsite);
   }
 
   StringId mapping_name;
@@ -108,7 +111,7 @@
   StringId build;
   StringId frame_name;
   TraceProcessorContext context;
-  std::unique_ptr<StackProfileTracker> stack_profile_tracker;
+  std::unique_ptr<SequenceStackProfileTracker> sequence_stack_profile_tracker;
 };
 
 // Insert the same mapping from two different packets, with different strings
@@ -116,10 +119,10 @@
 TEST_F(HeapProfileTrackerDupTest, Mapping) {
   InsertMapping(kFirstPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertMapping(kSecondPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   EXPECT_THAT(context.storage->stack_profile_mapping_table().build_id()[0],
               context.storage->InternString({kBuildIDHexName}));
@@ -142,10 +145,10 @@
 TEST_F(HeapProfileTrackerDupTest, Frame) {
   InsertFrame(kFirstPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertFrame(kSecondPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   const auto& frames = context.storage->stack_profile_frame_table();
   EXPECT_THAT(frames.name()[0], frame_name);
@@ -158,10 +161,10 @@
 TEST_F(HeapProfileTrackerDupTest, Callstack) {
   InsertCallsite(kFirstPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertCallsite(kSecondPacket);
   context.heap_profile_tracker->FinalizeProfile(
-      kDefaultSequence, stack_profile_tracker.get(), nullptr);
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   const auto& callsite_table = context.storage->stack_profile_callsite_table();
   const auto& depth = callsite_table.depth();
@@ -195,10 +198,12 @@
 TEST(HeapProfileTrackerTest, SourceMappingPath) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
+  context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
   context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
 
   HeapProfileTracker* hpt = context.heap_profile_tracker.get();
-  std::unique_ptr<StackProfileTracker> spt(new StackProfileTracker(&context));
+  std::unique_ptr<SequenceStackProfileTracker> spt(
+      new SequenceStackProfileTracker(&context));
 
   constexpr auto kBuildId = 1u;
   constexpr auto kMappingNameId1 = 2u;
@@ -208,7 +213,7 @@
   spt->AddString(kMappingNameId1, "foo");
   spt->AddString(kMappingNameId2, "bar");
 
-  StackProfileTracker::SourceMapping mapping;
+  SequenceStackProfileTracker::SourceMapping mapping;
   mapping.build_id = kBuildId;
   mapping.exact_offset = 1;
   mapping.start_offset = 1;
@@ -228,10 +233,12 @@
 TEST(HeapProfileTrackerTest, Functional) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
+  context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
   context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
 
   HeapProfileTracker* hpt = context.heap_profile_tracker.get();
-  std::unique_ptr<StackProfileTracker> spt(new StackProfileTracker(&context));
+  std::unique_ptr<SequenceStackProfileTracker> spt(
+      new SequenceStackProfileTracker(&context));
 
   uint32_t next_string_intern_id = 1;
 
@@ -245,8 +252,8 @@
   for (size_t i = 0; i < base::ArraySize(mapping_names); ++i)
     mapping_name_ids[i] = next_string_intern_id++;
 
-  StackProfileTracker::SourceMapping mappings[base::ArraySize(mapping_names)] =
-      {};
+  SequenceStackProfileTracker::SourceMapping
+      mappings[base::ArraySize(mapping_names)] = {};
   mappings[0].build_id = build_id_ids[0];
   mappings[0].exact_offset = 1;
   mappings[0].start_offset = 1;
@@ -276,7 +283,8 @@
   for (size_t i = 0; i < base::ArraySize(function_names); ++i)
     function_name_ids[i] = next_string_intern_id++;
 
-  StackProfileTracker::SourceFrame frames[base::ArraySize(function_names)];
+  SequenceStackProfileTracker::SourceFrame
+      frames[base::ArraySize(function_names)];
   frames[0].name_id = function_name_ids[0];
   frames[0].mapping_id = 0;
   frames[0].rel_pc = 123;
@@ -293,7 +301,7 @@
   frames[3].mapping_id = 2;
   frames[3].rel_pc = 123;
 
-  StackProfileTracker::SourceCallstack callstacks[3];
+  SequenceStackProfileTracker::SourceCallstack callstacks[3];
   callstacks[0] = {2, 1, 0};
   callstacks[1] = {2, 1, 0, 1, 0};
   callstacks[2] = {0, 2, 0, 1, 2};
@@ -324,7 +332,8 @@
 
   for (size_t i = 0; i < base::ArraySize(callstacks); ++i) {
     base::Optional<CallsiteId> parent;
-    const StackProfileTracker::SourceCallstack& callstack = callstacks[i];
+    const SequenceStackProfileTracker::SourceCallstack& callstack =
+        callstacks[i];
     for (size_t depth = 0; depth < callstack.size(); ++depth) {
       auto frame_id = spt->GetDatabaseFrameIdForTesting(callstack[depth]);
       base::Optional<CallsiteId> self = FindCallstack(
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc b/src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc
new file mode 100644
index 0000000..cd4dde2
--- /dev/null
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+using perfetto::protos::pbzero::TracePacket;
+
+MemoryTrackerSnapshotModule::MemoryTrackerSnapshotModule(
+    TraceProcessorContext* context)
+    : parser_(context) {
+  RegisterForField(TracePacket::kMemoryTrackerSnapshotFieldNumber, context);
+}
+
+MemoryTrackerSnapshotModule::~MemoryTrackerSnapshotModule() = default;
+
+void MemoryTrackerSnapshotModule::ParsePacket(
+    const TracePacket::Decoder& decoder,
+    const TimestampedTracePiece& ttp,
+    uint32_t field_id) {
+  switch (field_id) {
+    case TracePacket::kMemoryTrackerSnapshotFieldNumber:
+      parser_.ParseMemoryTrackerSnapshot(ttp.timestamp,
+                                         decoder.memory_tracker_snapshot());
+      return;
+  }
+}
+
+void MemoryTrackerSnapshotModule::NotifyEndOfFile() {
+  parser_.NotifyEndOfFile();
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_module.h b/src/trace_processor/importers/proto/memory_tracker_snapshot_module.h
new file mode 100644
index 0000000..730b7e3
--- /dev/null
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_module.h
@@ -0,0 +1,48 @@
+/*
+ * 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_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_MODULE_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_MODULE_H_
+
+#include "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h"
+#include "src/trace_processor/importers/proto/proto_importer_module.h"
+#include "src/trace_processor/timestamped_trace_piece.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class MemoryTrackerSnapshotModule : public ProtoImporterModule {
+ public:
+  explicit MemoryTrackerSnapshotModule(TraceProcessorContext* context);
+
+  ~MemoryTrackerSnapshotModule() override;
+
+  void ParsePacket(const protos::pbzero::TracePacket::Decoder&,
+                   const TimestampedTracePiece&,
+                   uint32_t field_id) override;
+
+  void NotifyEndOfFile() override;
+
+ private:
+  MemoryTrackerSnapshotParser parser_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_MODULE_H_
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
new file mode 100644
index 0000000..7d03af6
--- /dev/null
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h"
+
+#include "perfetto/ext/base/string_view.h"
+#include "protos/perfetto/trace/memory_graph.pbzero.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/tables/memory_tables.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+MemoryTrackerSnapshotParser::MemoryTrackerSnapshotParser(
+    TraceProcessorContext* context)
+    : context_(context),
+      level_of_detail_ids_{{context_->storage->InternString("detailed"),
+                            context_->storage->InternString("light"),
+                            context_->storage->InternString("background")}},
+      unit_ids_{{context_->storage->InternString("objects"),
+                 context_->storage->InternString("bytes")}},
+      aggregate_raw_nodes_(),
+      last_snapshot_timestamp_(-1),
+      last_snapshot_level_of_detail_(LevelOfDetail::kFirst) {}
+
+void MemoryTrackerSnapshotParser::ParseMemoryTrackerSnapshot(int64_t ts,
+                                                             ConstBytes blob) {
+  PERFETTO_DCHECK(last_snapshot_timestamp_ <= ts);
+  if (!aggregate_raw_nodes_.empty() && ts != last_snapshot_timestamp_) {
+    GenerateGraphFromRawNodesAndEmitRows();
+  }
+  ReadProtoSnapshot(blob, aggregate_raw_nodes_, last_snapshot_level_of_detail_);
+  last_snapshot_timestamp_ = ts;
+}
+
+void MemoryTrackerSnapshotParser::NotifyEndOfFile() {
+  if (!aggregate_raw_nodes_.empty()) {
+    GenerateGraphFromRawNodesAndEmitRows();
+  }
+}
+
+void MemoryTrackerSnapshotParser::ReadProtoSnapshot(
+    ConstBytes blob,
+    RawMemoryNodeMap& raw_nodes,
+    LevelOfDetail& level_of_detail) {
+  protos::pbzero::MemoryTrackerSnapshot::Decoder snapshot(blob.data, blob.size);
+  level_of_detail = LevelOfDetail::kDetailed;
+
+  switch (snapshot.level_of_detail()) {
+    case 0:  // FULL
+      level_of_detail = LevelOfDetail::kDetailed;
+      break;
+    case 1:  // LIGHT
+      level_of_detail = LevelOfDetail::kLight;
+      break;
+    case 2:  // BACKGROUND
+      level_of_detail = LevelOfDetail::kBackground;
+      break;
+  }
+
+  for (auto process_it = snapshot.process_memory_dumps(); process_it;
+       ++process_it) {
+    protos::pbzero::MemoryTrackerSnapshot::ProcessSnapshot::Decoder
+        process_memory_dump(*process_it);
+
+    base::PlatformProcessId pid =
+        static_cast<base::PlatformProcessId>(process_memory_dump.pid());
+
+    RawProcessMemoryNode::MemoryNodesMap nodes_map;
+    RawProcessMemoryNode::AllocatorNodeEdgesMap edges_map;
+
+    for (auto node_it = process_memory_dump.allocator_dumps(); node_it;
+         ++node_it) {
+      protos::pbzero::MemoryTrackerSnapshot::ProcessSnapshot::MemoryNode::
+          Decoder node(*node_it);
+
+      MemoryAllocatorNodeId node_id(node.id());
+      const std::string absolute_name = node.absolute_name().ToStdString();
+      int flags;
+      if (node.weak()) {
+        flags = RawMemoryGraphNode::kWeak;
+      } else {
+        flags = RawMemoryGraphNode::kDefault;
+      }
+
+      std::vector<RawMemoryGraphNode::MemoryNodeEntry> entries;
+
+      if (node.has_size_bytes()) {
+        entries.emplace_back("size", RawMemoryGraphNode::kUnitsBytes,
+                             node.size_bytes());
+      }
+
+      for (auto entry_it = node.entries(); entry_it; ++entry_it) {
+        protos::pbzero::MemoryTrackerSnapshot::ProcessSnapshot::MemoryNode::
+            MemoryNodeEntry::Decoder entry(*entry_it);
+
+        std::string unit;
+
+        switch (entry.units()) {
+          case 1:  // BYTES
+            unit = RawMemoryGraphNode::kUnitsBytes;
+            break;
+          case 2:  // COUNT
+            unit = RawMemoryGraphNode::kUnitsObjects;
+            break;
+        }
+        if (entry.has_value_uint64()) {
+          entries.emplace_back(entry.name().ToStdString(), unit,
+                               entry.value_uint64());
+        } else if (entry.has_value_string()) {
+          entries.emplace_back(entry.name().ToStdString(), unit,
+                               entry.value_string().ToStdString());
+        } else {
+          context_->storage->IncrementStats(
+              stats::memory_snapshot_parser_failure);
+        }
+      }
+      std::unique_ptr<RawMemoryGraphNode> raw_graph_node(new RawMemoryGraphNode(
+          absolute_name, level_of_detail, node_id, std::move(entries)));
+      raw_graph_node->set_flags(flags);
+      nodes_map.insert(
+          std::make_pair(absolute_name, std::move(raw_graph_node)));
+    }
+
+    for (auto edge_it = process_memory_dump.memory_edges(); edge_it;
+         ++edge_it) {
+      protos::pbzero::MemoryTrackerSnapshot::ProcessSnapshot::MemoryEdge::
+          Decoder edge(*edge_it);
+
+      std::unique_ptr<MemoryGraphEdge> graph_edge(new MemoryGraphEdge(
+          MemoryAllocatorNodeId(edge.source_id()),
+          MemoryAllocatorNodeId(edge.target_id()),
+          static_cast<int>(edge.importance()), edge.overridable()));
+
+      edges_map.insert(std::make_pair(MemoryAllocatorNodeId(edge.source_id()),
+                                      std::move(graph_edge)));
+    }
+    std::unique_ptr<RawProcessMemoryNode> raw_node(new RawProcessMemoryNode(
+        level_of_detail, std::move(edges_map), std::move(nodes_map)));
+    raw_nodes.insert(std::make_pair(pid, std::move(raw_node)));
+  }
+}
+
+std::unique_ptr<GlobalNodeGraph> MemoryTrackerSnapshotParser::GenerateGraph(
+    RawMemoryNodeMap& raw_nodes) {
+  auto graph = GraphProcessor::CreateMemoryGraph(raw_nodes);
+  GraphProcessor::CalculateSizesForGraph(graph.get());
+  return graph;
+}
+
+void MemoryTrackerSnapshotParser::EmitRows(int64_t ts,
+                                           GlobalNodeGraph& graph,
+                                           LevelOfDetail level_of_detail) {
+  IdNodeMap id_node_map;
+
+  // For now, we use the existing global instant event track for chrome events,
+  // since memory dumps are global.
+  TrackId track_id =
+      context_->track_tracker->GetOrCreateLegacyChromeGlobalInstantTrack();
+
+  tables::MemorySnapshotTable::Row snapshot_row(
+      ts, track_id, level_of_detail_ids_[static_cast<size_t>(level_of_detail)]);
+  tables::MemorySnapshotTable::Id snapshot_row_id =
+      context_->storage->mutable_memory_snapshot_table()
+          ->Insert(snapshot_row)
+          .id;
+
+  for (auto const& it_process : graph.process_node_graphs()) {
+    tables::ProcessMemorySnapshotTable::Row process_row;
+    process_row.upid = context_->process_tracker->GetOrCreateProcess(
+        static_cast<uint32_t>(it_process.first));
+    process_row.snapshot_id = snapshot_row_id;
+    tables::ProcessMemorySnapshotTable::Id proc_snapshot_row_id =
+        context_->storage->mutable_process_memory_snapshot_table()
+            ->Insert(process_row)
+            .id;
+    EmitMemorySnapshotNodeRows(*(it_process.second->root()),
+                               proc_snapshot_row_id, id_node_map);
+  }
+
+  // For each snapshot nodes from shared_memory_graph will be associated
+  // with a fabricated process_memory_snapshot entry whose pid == 0.
+  // TODO(mobica-google-contributors@mobica.com): Track the shared memory graph
+  // in a separate table.
+  tables::ProcessMemorySnapshotTable::Row fake_process_row;
+  fake_process_row.upid = context_->process_tracker->GetOrCreateProcess(0u);
+  fake_process_row.snapshot_id = snapshot_row_id;
+  tables::ProcessMemorySnapshotTable::Id fake_proc_snapshot_row_id =
+      context_->storage->mutable_process_memory_snapshot_table()
+          ->Insert(fake_process_row)
+          .id;
+  EmitMemorySnapshotNodeRows(*(graph.shared_memory_graph()->root()),
+                             fake_proc_snapshot_row_id, id_node_map);
+
+  for (const auto& edge : graph.edges()) {
+    tables::MemorySnapshotEdgeTable::Row edge_row;
+    auto source_it = id_node_map.find(edge.source()->id());
+    if (source_it == id_node_map.end())
+      continue;
+    edge_row.source_node_id =
+        static_cast<tables::MemorySnapshotNodeTable::Id>(source_it->second);
+    auto target_it = id_node_map.find(edge.target()->id());
+    if (target_it == id_node_map.end())
+      continue;
+    edge_row.target_node_id =
+        static_cast<tables::MemorySnapshotNodeTable::Id>(target_it->second);
+    edge_row.importance = static_cast<uint32_t>(edge.priority());
+    context_->storage->mutable_memory_snapshot_edge_table()->Insert(edge_row);
+  }
+}
+
+void MemoryTrackerSnapshotParser::EmitMemorySnapshotNodeRows(
+    GlobalNodeGraph::Node& root_node_graph,
+    ProcessMemorySnapshotId& proc_snapshot_row_id,
+    IdNodeMap& id_node_map) {
+  EmitMemorySnapshotNodeRowsRecursively(root_node_graph, std::string(),
+                                        base::nullopt, proc_snapshot_row_id,
+                                        id_node_map);
+}
+
+void MemoryTrackerSnapshotParser::EmitMemorySnapshotNodeRowsRecursively(
+    GlobalNodeGraph::Node& node,
+    const std::string& path,
+    base::Optional<tables::MemorySnapshotNodeTable::Id> parent_node_row_id,
+    ProcessMemorySnapshotId& proc_snapshot_row_id,
+    IdNodeMap& id_node_map) {
+  base::Optional<tables::MemorySnapshotNodeTable::Id> node_id;
+  // Skip emitting the root node into the tables - it is not a real node.
+  if (!path.empty()) {
+    node_id = EmitNode(node, path, parent_node_row_id, proc_snapshot_row_id,
+                       id_node_map);
+  }
+
+  for (const auto& name_and_child : *node.children()) {
+    std::string child_path = path;
+    if (!child_path.empty())
+      child_path += "/";
+    child_path += name_and_child.first;
+
+    EmitMemorySnapshotNodeRowsRecursively(*(name_and_child.second), child_path,
+                                          /*parent_node_id=*/node_id,
+                                          proc_snapshot_row_id, id_node_map);
+  }
+}
+
+base::Optional<tables::MemorySnapshotNodeTable::Id>
+MemoryTrackerSnapshotParser::EmitNode(
+    const GlobalNodeGraph::Node& node,
+    const std::string& path,
+    base::Optional<tables::MemorySnapshotNodeTable::Id> parent_node_row_id,
+    ProcessMemorySnapshotId& proc_snapshot_row_id,
+    IdNodeMap& id_node_map) {
+  tables::MemorySnapshotNodeTable::Row node_row;
+  node_row.process_snapshot_id = proc_snapshot_row_id;
+  node_row.parent_node_id = parent_node_row_id;
+  node_row.path = context_->storage->InternString(base::StringView(path));
+
+  tables::MemorySnapshotNodeTable::Id node_row_id =
+      context_->storage->mutable_memory_snapshot_node_table()
+          ->Insert(node_row)
+          .id;
+
+  auto* node_table = context_->storage->mutable_memory_snapshot_node_table();
+  uint32_t node_row_index =
+      static_cast<uint32_t>(*node_table->id().IndexOf(node_row_id));
+  ArgsTracker::BoundInserter args =
+      context_->args_tracker->AddArgsTo(node_row_id);
+
+  for (const auto& entry : node.const_entries()) {
+    switch (entry.second.type) {
+      case GlobalNodeGraph::Node::Entry::Type::kUInt64: {
+        int64_t value_int = static_cast<int64_t>(entry.second.value_uint64);
+
+        if (entry.first == "size") {
+          node_table->mutable_size()->Set(node_row_index, value_int);
+        } else if (entry.first == "effective_size") {
+          node_table->mutable_effective_size()->Set(node_row_index, value_int);
+        } else {
+          args.AddArg(context_->storage->InternString(
+                          base::StringView(entry.first + ".value")),
+                      Variadic::Integer(value_int));
+          if (entry.second.units < unit_ids_.size()) {
+            args.AddArg(context_->storage->InternString(
+                            base::StringView(entry.first + ".unit")),
+                        Variadic::String(unit_ids_[entry.second.units]));
+          }
+        }
+        break;
+      }
+      case GlobalNodeGraph::Node::Entry::Type::kString: {
+        args.AddArg(context_->storage->InternString(
+                        base::StringView(entry.first + ".value")),
+                    Variadic::String(context_->storage->InternString(
+                        base::StringView(entry.second.value_string))));
+        break;
+      }
+    }
+  }
+  id_node_map.emplace(std::make_pair(node.id(), node_row_id));
+  return node_row_id;
+}
+
+void MemoryTrackerSnapshotParser::GenerateGraphFromRawNodesAndEmitRows() {
+  std::unique_ptr<GlobalNodeGraph> graph = GenerateGraph(aggregate_raw_nodes_);
+  EmitRows(last_snapshot_timestamp_, *(graph.get()),
+           last_snapshot_level_of_detail_);
+  aggregate_raw_nodes_.clear();
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h
new file mode 100644
index 0000000..32119c2
--- /dev/null
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.h
@@ -0,0 +1,122 @@
+/*
+ * 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_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_PARSER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_PARSER_H_
+
+#include "perfetto/ext/trace_processor/importers/memory_tracker/graph_processor.h"
+#include "protos/perfetto/trace/memory_graph.pbzero.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class MemoryTrackerSnapshotParser {
+ public:
+  explicit MemoryTrackerSnapshotParser(TraceProcessorContext* context);
+  void ParseMemoryTrackerSnapshot(int64_t ts, protozero::ConstBytes blob);
+
+  void NotifyEndOfFile();
+
+ private:
+  using RawMemoryNodeMap =
+      std::map<base::PlatformProcessId, std::unique_ptr<RawProcessMemoryNode>>;
+  using IdNodeMap =
+      std::map<MemoryAllocatorNodeId, tables::MemorySnapshotNodeTable::Id>;
+  using ConstBytes = protozero::ConstBytes;
+
+  class ChildNode {
+   public:
+    ChildNode() : table_index_(-1) {}
+    GlobalNodeGraph::Node* node_;
+    std::string path_;
+    uint64_t size_;
+    uint64_t effective_size_;
+    int32_t table_index_;
+  };
+
+  // Reads the proto-encoded memory snapshot of a process (message
+  // MemoryTrackerSnapshot) in given |blob| in order to get:
+  // - map of RawProcessMemoryNode containers |raw_nodes|. It
+  //   is need to generates GlobalNodeGraph via GraphProcessor.
+  // - level of detail of the memory graph |level_of_detail|.
+  void ReadProtoSnapshot(ConstBytes blob,
+                         RawMemoryNodeMap& raw_nodes,
+                         LevelOfDetail& level_of_detail);
+
+  // Generates GlobalNodeGraph via GraphProcessor for given map |raw_nodes|.
+  std::unique_ptr<GlobalNodeGraph> GenerateGraph(RawMemoryNodeMap& raw_nodes);
+
+  // Fills out MemorySnapshotTable, ProcessMemorySnapshotTable,
+  // MemorySnapshotNodeTable, MemorySnapshotEdgeTable with given
+  // timestamp |ts|, |graph|, |level_of_detail|.
+  void EmitRows(int64_t ts,
+                GlobalNodeGraph& graph,
+                LevelOfDetail level_of_detail);
+
+  // Fills out MemorySnapshotNodeTable for given root node
+  // |root_node_graph| and ProcessMemorySnapshotId |proc_snapshot_row_id|.
+  // Generates map of MemoryAllocatorNodeId and MemorySnapshotNodeTable::Id
+  // |id_node_map| which is used at time of filling out of
+  // MemorySnapshotEdgeTable.
+  void EmitMemorySnapshotNodeRows(GlobalNodeGraph::Node& root_node_graph,
+                                  ProcessMemorySnapshotId& proc_snapshot_row_id,
+                                  IdNodeMap& id_node_map);
+
+  // Recursively traverses through list of children of |node| to generate full
+  // |path| to every node in MemorySnapshotNodeTable for given
+  // ProcessMemorySnapshotId |proc_snapshot_row_id|.
+  // Generates map of MemoryAllocatorNodeId and MemorySnapshotNodeTable::Id
+  // |id_node_map| which is used at time of filling out of
+  // MemorySnapshotEdgeTable.
+  void EmitMemorySnapshotNodeRowsRecursively(
+      GlobalNodeGraph::Node& node,
+      const std::string&,
+      base::Optional<tables::MemorySnapshotNodeTable::Id> parent_node_row_id,
+      ProcessMemorySnapshotId& proc_snapshot_row_id,
+      IdNodeMap& id_node_map);
+
+  // Fills out MemorySnapshotNodeTable for given Node
+  // |node|, |path|, MemorySnapshotNodeTable::Id |parent_node_row_id| and
+  // ProcessMemorySnapshotId |proc_snapshot_row_id|. Generates map of
+  // MemoryAllocatorNodeId and MemorySnapshotNodeTable::Id |id_node_map| which
+  // is used at time of filling out of MemorySnapshotEdgeTable.
+  base::Optional<tables::MemorySnapshotNodeTable::Id> EmitNode(
+      const GlobalNodeGraph::Node& node,
+      const std::string& path,
+      base::Optional<tables::MemorySnapshotNodeTable::Id> parent_node_row_id,
+      ProcessMemorySnapshotId& proc_snapshot_row_id,
+      IdNodeMap& id_node_map);
+
+  void GenerateGraphFromRawNodesAndEmitRows();
+
+  TraceProcessorContext* context_;
+  std::array<StringId, 3> level_of_detail_ids_;
+  std::array<StringId, 2> unit_ids_;
+  RawMemoryNodeMap aggregate_raw_nodes_;
+  int64_t last_snapshot_timestamp_;
+  LevelOfDetail last_snapshot_level_of_detail_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_MEMORY_TRACKER_SNAPSHOT_PARSER_H_
diff --git a/src/trace_processor/importers/proto/metadata_tracker.cc b/src/trace_processor/importers/proto/metadata_tracker.cc
index 0162392..64f68e8 100644
--- a/src/trace_processor/importers/proto/metadata_tracker.cc
+++ b/src/trace_processor/importers/proto/metadata_tracker.cc
@@ -55,6 +55,17 @@
   return id_and_row.id;
 }
 
+SqlValue MetadataTracker::GetMetadataForTesting(metadata::KeyId key) {
+  // KeyType::kMulti not yet supported by this method:
+  PERFETTO_CHECK(metadata::kKeyTypes[key] == metadata::KeyType::kSingle);
+
+  auto* metadata_table = context_->storage->mutable_metadata_table();
+  uint32_t key_idx = static_cast<uint32_t>(key);
+  uint32_t row =
+      metadata_table->name().IndexOf(metadata::kNames[key_idx]).value();
+  return metadata_table->mutable_str_value()->Get(row);
+}
+
 MetadataId MetadataTracker::AppendMetadata(metadata::KeyId key,
                                            Variadic value) {
   PERFETTO_DCHECK(key < metadata::kNumKeys);
diff --git a/src/trace_processor/importers/proto/metadata_tracker.h b/src/trace_processor/importers/proto/metadata_tracker.h
index fc8bba2..8e8e0ef 100644
--- a/src/trace_processor/importers/proto/metadata_tracker.h
+++ b/src/trace_processor/importers/proto/metadata_tracker.h
@@ -41,6 +41,10 @@
   // Returns the id of the new entry.
   MetadataId AppendMetadata(metadata::KeyId key, Variadic value);
 
+  // Reads back a set metadata value.
+  // For use in tests only.
+  SqlValue GetMetadataForTesting(metadata::KeyId key);
+
  private:
   static constexpr size_t kNumKeys =
       static_cast<size_t>(metadata::KeyId::kNumKeys);
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index 1bd9cae..c0a5fde 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -86,7 +86,7 @@
       PERFETTO_FATAL(
           "Interning entry accessed under different types! previous type: "
           "%s. new type: %s.",
-          decoder_type_, __PRETTY_FUNCTION__);
+          decoder_type_, PERFETTO_DEBUG_FUNCTION_IDENTIFIER());
     }
     return reinterpret_cast<typename MessageType::Decoder*>(decoder_.get());
   }
@@ -190,6 +190,7 @@
   }
 
   PacketSequenceState* state() const { return state_; }
+  size_t generation_index() const { return generation_index_; }
 
  private:
   friend class PacketSequenceState;
@@ -224,9 +225,9 @@
 class PacketSequenceState {
  public:
   PacketSequenceState(TraceProcessorContext* context)
-      : context_(context), stack_profile_tracker_(context) {
-    generations_.emplace_back(
-        new PacketSequenceStateGeneration(this, generations_.size()));
+      : context_(context), sequence_stack_profile_tracker_(context) {
+    current_generation_.reset(
+        new PacketSequenceStateGeneration(this, generation_index_++));
   }
 
   int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
@@ -249,23 +250,23 @@
 
   // Intern a message into the current generation.
   void InternMessage(uint32_t field_id, TraceBlobView message) {
-    generations_.back()->InternMessage(field_id, std::move(message));
+    current_generation_->InternMessage(field_id, std::move(message));
   }
 
   // Set the trace packet defaults for the current generation. If the current
   // generation already has defaults set, starts a new generation without
   // invalidating other incremental state (such as interned data).
   void UpdateTracePacketDefaults(TraceBlobView defaults) {
-    if (!generations_.back()->GetTracePacketDefaultsView()) {
-      generations_.back()->SetTracePacketDefaults(std::move(defaults));
+    if (!current_generation_->GetTracePacketDefaultsView()) {
+      current_generation_->SetTracePacketDefaults(std::move(defaults));
       return;
     }
 
     // The new defaults should only apply to subsequent messages on the
     // sequence. Add a new generation with the updated defaults but the
     // current generation's interned data state.
-    generations_.emplace_back(new PacketSequenceStateGeneration(
-        this, generations_.size(), generations_.back()->interned_data_,
+    current_generation_.reset(new PacketSequenceStateGeneration(
+        this, generation_index_++, current_generation_->interned_data_,
         std::move(defaults)));
   }
 
@@ -291,19 +292,19 @@
   // Starts a new generation with clean-slate incremental state and defaults.
   void OnIncrementalStateCleared() {
     packet_loss_ = false;
-    generations_.emplace_back(
-        new PacketSequenceStateGeneration(this, generations_.size()));
+    current_generation_.reset(
+        new PacketSequenceStateGeneration(this, generation_index_++));
   }
 
   bool IsIncrementalStateValid() const { return !packet_loss_; }
 
-  StackProfileTracker& stack_profile_tracker() {
-    return stack_profile_tracker_;
+  SequenceStackProfileTracker& sequence_stack_profile_tracker() {
+    return sequence_stack_profile_tracker_;
   }
 
-  // Returns a pointer to the current generation.
-  PacketSequenceStateGeneration* current_generation() const {
-    return generations_.back().get();
+  // Returns a ref-counted ptr to the current generation.
+  std::shared_ptr<PacketSequenceStateGeneration> current_generation() const {
+    return current_generation_;
   }
 
   bool track_event_timestamps_valid() const {
@@ -318,13 +319,10 @@
   TraceProcessorContext* context() const { return context_; }
 
  private:
-  // TODO(eseckler): Reference count the generations so that we can get rid of
-  // past generations once all packets referring to them have been parsed.
-  using GenerationList =
-      std::vector<std::unique_ptr<PacketSequenceStateGeneration>>;
-
   TraceProcessorContext* context_;
 
+  size_t generation_index_ = 0;
+
   // If true, incremental state on the sequence is considered invalid until we
   // see the next packet with incremental_state_cleared. We assume that we
   // missed some packets at the beginning of the trace.
@@ -350,8 +348,8 @@
   int64_t track_event_thread_timestamp_ns_ = 0;
   int64_t track_event_thread_instruction_count_ = 0;
 
-  GenerationList generations_;
-  StackProfileTracker stack_profile_tracker_;
+  std::shared_ptr<PacketSequenceStateGeneration> current_generation_;
+  SequenceStackProfileTracker sequence_stack_profile_tracker_;
 };
 
 template <uint32_t FieldId, typename MessageType>
@@ -367,9 +365,7 @@
   }
   state_->context()->storage->IncrementStats(
       stats::interned_data_tokenizer_errors);
-  PERFETTO_DLOG("Could not find interning entry for field ID %" PRIu32
-                ", generation %zu, and IID %" PRIu64,
-                FieldId, generation_index_, iid);
+  base::ignore_result(generation_index_);
   return nullptr;
 }
 
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index 6b9b849..d60cd2f 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -63,7 +63,8 @@
   switch (field_id) {
     case TracePacket::kStreamingProfilePacketFieldNumber:
       PERFETTO_DCHECK(ttp.type == TimestampedTracePiece::Type::kTracePacket);
-      ParseStreamingProfilePacket(ttp.timestamp, ttp.packet_data.sequence_state,
+      ParseStreamingProfilePacket(ttp.timestamp,
+                                  ttp.packet_data.sequence_state.get(),
                                   decoder.streaming_profile_packet());
       return;
   }
@@ -112,8 +113,8 @@
 
   ProcessTracker* procs = context_->process_tracker.get();
   TraceStorage* storage = context_->storage.get();
-  StackProfileTracker& stack_profile_tracker =
-      sequence_state->state()->stack_profile_tracker();
+  SequenceStackProfileTracker& sequence_stack_profile_tracker =
+      sequence_state->state()->sequence_stack_profile_tracker();
   ProfilePacketInternLookup intern_lookup(sequence_state);
 
   uint32_t pid = static_cast<uint32_t>(sequence_state->state()->pid());
@@ -131,7 +132,7 @@
       break;
     }
 
-    auto opt_cs_id = stack_profile_tracker.FindOrInsertCallstack(
+    auto opt_cs_id = sequence_stack_profile_tracker.FindOrInsertCallstack(
         *callstack_it, &intern_lookup);
     if (!opt_cs_id) {
       context_->storage->IncrementStats(stats::stackprofile_parser_error);
diff --git a/src/trace_processor/importers/proto/profile_packet_utils.h b/src/trace_processor/importers/proto/profile_packet_utils.h
index 41c6535..731eb35 100644
--- a/src/trace_processor/importers/proto/profile_packet_utils.h
+++ b/src/trace_processor/importers/proto/profile_packet_utils.h
@@ -31,9 +31,9 @@
 
 class ProfilePacketUtils {
  public:
-  static StackProfileTracker::SourceMapping MakeSourceMapping(
+  static SequenceStackProfileTracker::SourceMapping MakeSourceMapping(
       const protos::pbzero::Mapping::Decoder& entry) {
-    StackProfileTracker::SourceMapping src_mapping{};
+    SequenceStackProfileTracker::SourceMapping src_mapping{};
     src_mapping.build_id = entry.build_id();
     src_mapping.exact_offset = entry.exact_offset();
     src_mapping.start_offset = entry.start_offset();
@@ -47,18 +47,18 @@
     return src_mapping;
   }
 
-  static StackProfileTracker::SourceFrame MakeSourceFrame(
+  static SequenceStackProfileTracker::SourceFrame MakeSourceFrame(
       const protos::pbzero::Frame::Decoder& entry) {
-    StackProfileTracker::SourceFrame src_frame;
+    SequenceStackProfileTracker::SourceFrame src_frame;
     src_frame.name_id = entry.function_name_id();
     src_frame.mapping_id = entry.mapping_id();
     src_frame.rel_pc = entry.rel_pc();
     return src_frame;
   }
 
-  static StackProfileTracker::SourceCallstack MakeSourceCallstack(
+  static SequenceStackProfileTracker::SourceCallstack MakeSourceCallstack(
       const protos::pbzero::Callstack::Decoder& entry) {
-    StackProfileTracker::SourceCallstack src_callstack;
+    SequenceStackProfileTracker::SourceCallstack src_callstack;
     for (auto frame_it = entry.frame_ids(); frame_it; ++frame_it)
       src_callstack.emplace_back(*frame_it);
     return src_callstack;
@@ -107,33 +107,40 @@
         return "repeated_frame";
       case Profiling::UNWIND_ERROR_INVALID_ELF:
         return "invalid_elf";
+      case Profiling::UNWIND_ERROR_SYSTEM_CALL:
+        return "system_call";
+      case Profiling::UNWIND_ERROR_THREAD_TIMEOUT:
+        return "thread_timeout";
+      case Profiling::UNWIND_ERROR_THREAD_DOES_NOT_EXIST:
+        return "thread_does_not_exist";
     }
     return "unknown";  // switch should be complete, but gcc needs a hint
   }
 };
 
-class ProfilePacketInternLookup : public StackProfileTracker::InternLookup {
+class ProfilePacketInternLookup
+    : public SequenceStackProfileTracker::InternLookup {
  public:
   explicit ProfilePacketInternLookup(PacketSequenceStateGeneration* seq_state)
       : seq_state_(seq_state) {}
   ~ProfilePacketInternLookup() override;
 
   base::Optional<base::StringView> GetString(
-      StackProfileTracker::SourceStringId iid,
-      StackProfileTracker::InternedStringType type) const override {
+      SequenceStackProfileTracker::SourceStringId iid,
+      SequenceStackProfileTracker::InternedStringType type) const override {
     protos::pbzero::InternedString::Decoder* decoder = nullptr;
     switch (type) {
-      case StackProfileTracker::InternedStringType::kBuildId:
+      case SequenceStackProfileTracker::InternedStringType::kBuildId:
         decoder = seq_state_->LookupInternedMessage<
             protos::pbzero::InternedData::kBuildIdsFieldNumber,
             protos::pbzero::InternedString>(iid);
         break;
-      case StackProfileTracker::InternedStringType::kFunctionName:
+      case SequenceStackProfileTracker::InternedStringType::kFunctionName:
         decoder = seq_state_->LookupInternedMessage<
             protos::pbzero::InternedData::kFunctionNamesFieldNumber,
             protos::pbzero::InternedString>(iid);
         break;
-      case StackProfileTracker::InternedStringType::kMappingPath:
+      case SequenceStackProfileTracker::InternedStringType::kMappingPath:
         decoder = seq_state_->LookupInternedMessage<
             protos::pbzero::InternedData::kMappingPathsFieldNumber,
             protos::pbzero::InternedString>(iid);
@@ -145,8 +152,8 @@
                             decoder->str().size);
   }
 
-  base::Optional<StackProfileTracker::SourceMapping> GetMapping(
-      StackProfileTracker::SourceMappingId iid) const override {
+  base::Optional<SequenceStackProfileTracker::SourceMapping> GetMapping(
+      SequenceStackProfileTracker::SourceMappingId iid) const override {
     auto* decoder = seq_state_->LookupInternedMessage<
         protos::pbzero::InternedData::kMappingsFieldNumber,
         protos::pbzero::Mapping>(iid);
@@ -155,8 +162,8 @@
     return ProfilePacketUtils::MakeSourceMapping(*decoder);
   }
 
-  base::Optional<StackProfileTracker::SourceFrame> GetFrame(
-      StackProfileTracker::SourceFrameId iid) const override {
+  base::Optional<SequenceStackProfileTracker::SourceFrame> GetFrame(
+      SequenceStackProfileTracker::SourceFrameId iid) const override {
     auto* decoder = seq_state_->LookupInternedMessage<
         protos::pbzero::InternedData::kFramesFieldNumber,
         protos::pbzero::Frame>(iid);
@@ -165,8 +172,8 @@
     return ProfilePacketUtils::MakeSourceFrame(*decoder);
   }
 
-  base::Optional<StackProfileTracker::SourceCallstack> GetCallstack(
-      StackProfileTracker::SourceCallstackId iid) const override {
+  base::Optional<SequenceStackProfileTracker::SourceCallstack> GetCallstack(
+      SequenceStackProfileTracker::SourceCallstackId iid) const override {
     auto* decoder = seq_state_->LookupInternedMessage<
         protos::pbzero::InternedData::kCallstacksFieldNumber,
         protos::pbzero::Callstack>(iid);
diff --git a/src/trace_processor/importers/proto/profiler_util.cc b/src/trace_processor/importers/proto/profiler_util.cc
new file mode 100644
index 0000000..83decc5
--- /dev/null
+++ b/src/trace_processor/importers/proto/profiler_util.cc
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/profiler_util.h"
+
+#include "perfetto/ext/base/optional.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+base::Optional<base::StringView> PackageFromApp(base::StringView location) {
+  location = location.substr(base::StringView("/data/app/").size());
+  size_t start = 0;
+  if (location.at(0) == '~') {
+    size_t slash = location.find('/');
+    if (slash == std::string::npos) {
+      return base::nullopt;
+    }
+    start = slash + 1;
+  }
+  size_t end = location.find('/', start + 1);
+  if (end == std::string::npos) {
+    return base::nullopt;
+  }
+  location = location.substr(start, end);
+  size_t minus = location.find('-');
+  if (minus == std::string::npos) {
+    return base::nullopt;
+  }
+  return location.substr(0, minus);
+}
+
+}  // namespace
+
+base::Optional<std::string> PackageFromLocation(TraceStorage* storage,
+                                                base::StringView location) {
+  // List of some hardcoded apps that do not follow the scheme used in
+  // PackageFromApp. Ask for yours to be added.
+  //
+  // TODO(b/153632336): Get rid of the hardcoded list of system apps.
+  base::StringView sysui(
+      "/system_ext/priv-app/SystemUIGoogle/SystemUIGoogle.apk");
+  if (location.size() >= sysui.size() &&
+      location.substr(0, sysui.size()) == sysui) {
+    return "com.android.systemui";
+  }
+
+  base::StringView phonesky("/product/priv-app/Phonesky/Phonesky.apk");
+  if (location.size() >= phonesky.size() &&
+      location.substr(0, phonesky.size()) == phonesky) {
+    return "com.android.vending";
+  }
+
+  base::StringView maps("/product/app/Maps/Maps.apk");
+  if (location.size() >= maps.size() &&
+      location.substr(0, maps.size()) == maps) {
+    return "com.google.android.apps.maps";
+  }
+
+  base::StringView launcher(
+      "/system_ext/priv-app/NexusLauncherRelease/NexusLauncherRelease.apk");
+  if (location.size() >= launcher.size() &&
+      location.substr(0, launcher.size()) == launcher) {
+    return "com.google.android.apps.nexuslauncher";
+  }
+
+  base::StringView photos("/product/app/Photos/Photos.apk");
+  if (location.size() >= photos.size() &&
+      location.substr(0, photos.size()) == photos) {
+    return "com.google.android.apps.photos";
+  }
+
+  base::StringView wellbeing(
+      "/product/priv-app/WellbeingPrebuilt/WellbeingPrebuilt.apk");
+  if (location.size() >= wellbeing.size() &&
+      location.substr(0, wellbeing.size()) == wellbeing) {
+    return "com.google.android.apps.wellbeing";
+  }
+
+  base::StringView matchmaker("MatchMaker");
+  if (location.size() >= matchmaker.size() &&
+      location.find(matchmaker) != base::StringView::npos) {
+    return "com.google.android.as";
+  }
+
+  base::StringView gm("/product/app/PrebuiltGmail/PrebuiltGmail.apk");
+  if (location.size() >= gm.size() && location.substr(0, gm.size()) == gm) {
+    return "com.google.android.gm";
+  }
+
+  base::StringView gmscore("/product/priv-app/PrebuiltGmsCore/PrebuiltGmsCore");
+  if (location.size() >= gmscore.size() &&
+      location.substr(0, gmscore.size()) == gmscore) {
+    return "com.google.android.gms";
+  }
+
+  base::StringView velvet("/product/priv-app/Velvet/Velvet.apk");
+  if (location.size() >= velvet.size() &&
+      location.substr(0, velvet.size()) == velvet) {
+    return "com.google.android.googlequicksearchbox";
+  }
+
+  base::StringView inputmethod(
+      "/product/app/LatinIMEGooglePrebuilt/LatinIMEGooglePrebuilt.apk");
+  if (location.size() >= inputmethod.size() &&
+      location.substr(0, inputmethod.size()) == inputmethod) {
+    return "com.google.android.inputmethod.latin";
+  }
+
+  base::StringView messaging("/product/app/PrebuiltBugle/PrebuiltBugle.apk");
+  if (location.size() >= messaging.size() &&
+      location.substr(0, messaging.size()) == messaging) {
+    return "com.google.android.apps.messaging";
+  }
+
+  base::StringView data_app("/data/app/");
+  if (location.substr(0, data_app.size()) == data_app) {
+    auto package = PackageFromApp(location);
+    if (!package) {
+      PERFETTO_DLOG("Failed to parse %s", location.ToStdString().c_str());
+      storage->IncrementStats(stats::deobfuscate_location_parse_error);
+      return base::nullopt;
+    }
+    return package->ToStdString();
+  }
+  return base::nullopt;
+}
+
+std::string FullyQualifiedDeobfuscatedName(
+    protos::pbzero::ObfuscatedClass::Decoder& cls,
+    protos::pbzero::ObfuscatedMember::Decoder& member) {
+  std::string member_deobfuscated_name =
+      member.deobfuscated_name().ToStdString();
+  if (member_deobfuscated_name.find('.') == std::string::npos) {
+    // Name relative to class.
+    return cls.deobfuscated_name().ToStdString() + "." +
+           member_deobfuscated_name;
+  } else {
+    // Fully qualified name.
+    return member_deobfuscated_name;
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/profiler_util.h b/src/trace_processor/importers/proto/profiler_util.h
new file mode 100644
index 0000000..3870340
--- /dev/null
+++ b/src/trace_processor/importers/proto/profiler_util.h
@@ -0,0 +1,41 @@
+/*
+ * 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_IMPORTERS_PROTO_PROFILER_UTIL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILER_UTIL_H_
+
+#include <string>
+
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_view.h"
+
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+std::string FullyQualifiedDeobfuscatedName(
+    protos::pbzero::ObfuscatedClass::Decoder& cls,
+    protos::pbzero::ObfuscatedMember::Decoder& member);
+
+base::Optional<std::string> PackageFromLocation(TraceStorage* storage,
+                                                base::StringView location);
+
+}
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILER_UTIL_H_
diff --git a/src/trace_processor/importers/proto/proto_importer_module.h b/src/trace_processor/importers/proto/proto_importer_module.h
index 909eb14..dda2f85 100644
--- a/src/trace_processor/importers/proto/proto_importer_module.h
+++ b/src/trace_processor/importers/proto/proto_importer_module.h
@@ -36,7 +36,7 @@
 struct TimestampedTracePiece;
 class TraceProcessorContext;
 
-// This file contains a base class for ProtoTraceTokenizer/Parser modules.
+// This file contains a base class for ProtoTraceReader/Parser modules.
 // A module implements support for a subset of features of the TracePacket
 // proto format.
 // To add and integrate a new module:
@@ -98,7 +98,7 @@
 
   virtual ~ProtoImporterModule();
 
-  // Called by ProtoTraceTokenizer during the tokenization stage, i.e. before
+  // Called by ProtoTraceReader during the tokenization stage, i.e. before
   // sorting. It's called for each TracePacket that contains fields for which
   // the module was registered. If this returns a result other than
   // ModuleResult::Ignored(), tokenization of the packet will be aborted after
@@ -110,6 +110,13 @@
       PacketSequenceState*,
       uint32_t field_id);
 
+  // Called by ProtoTraceReader during the tokenization stage i.e. before
+  // sorting. Indicates that sequence with id |packet_sequence_id| has cleared
+  // its incremental state. This should be used to clear any cached state the
+  // tokenizer has built up while reading packets until this point for this
+  // packet sequence.
+  virtual void OnIncrementalStateCleared(uint32_t /* packet_sequence_id */) {}
+
   // Called by ProtoTraceParser after the sorting stage for each non-ftrace
   // TracePacket that contains fields for which the module was registered.
   virtual void ParsePacket(const protos::pbzero::TracePacket_Decoder&,
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index 1184aea..b93af99 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -29,22 +29,28 @@
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/uuid.h"
 #include "perfetto/trace_processor/status.h"
+
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/config.descriptor.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
 #include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/descriptors.h"
+#include "src/trace_processor/util/protozero_to_text.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/common/trace_stats.pbzero.h"
@@ -53,6 +59,7 @@
 #include "protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
 #include "protos/perfetto/trace/profiling/smaps.pbzero.h"
@@ -93,7 +100,7 @@
   const TraceBlobView& blob = data->packet;
   protos::pbzero::TracePacket::Decoder packet(blob.data(), blob.length());
 
-  ParseTracePacketImpl(ts, std::move(ttp), data, packet);
+  ParseTracePacketImpl(ts, ttp, data->sequence_state.get(), packet);
 
   // TODO(lalitm): maybe move this to the flush method in the trace processor
   // once we have it. This may reduce performance in the ArgsTracker though so
@@ -104,9 +111,17 @@
 
 void ProtoTraceParser::ParseTracePacketImpl(
     int64_t ts,
-    TimestampedTracePiece ttp,
-    const TracePacketData* data,
+    const TimestampedTracePiece& ttp,
+    PacketSequenceStateGeneration* sequence_state,
     const protos::pbzero::TracePacket::Decoder& packet) {
+  // This needs to get handled both by the HeapGraphModule and
+  // ProtoTraceParser (for StackProfileTracker).
+  if (packet.has_deobfuscation_mapping()) {
+    ParseDeobfuscationMapping(ts, sequence_state,
+                              packet.trusted_packet_sequence_id(),
+                              packet.deobfuscation_mapping());
+  }
+
   // TODO(eseckler): Propagate statuses from modules.
   auto& modules = context_->modules_by_field;
   for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
@@ -120,13 +135,12 @@
     ParseTraceStats(packet.trace_stats());
 
   if (packet.has_profile_packet()) {
-    ParseProfilePacket(ts, data->sequence_state,
-                       packet.trusted_packet_sequence_id(),
+    ParseProfilePacket(ts, sequence_state, packet.trusted_packet_sequence_id(),
                        packet.profile_packet());
   }
 
   if (packet.has_perf_sample()) {
-    ParsePerfSample(ts, data->sequence_state, packet.perf_sample());
+    ParsePerfSample(ts, sequence_state, packet.perf_sample());
   }
 
   if (packet.has_chrome_benchmark_metadata()) {
@@ -248,31 +262,31 @@
 
     const char* str = reinterpret_cast<const char*>(entry.str().data);
     auto str_view = base::StringView(str, entry.str().size);
-    sequence_state->state()->stack_profile_tracker().AddString(entry.iid(),
-                                                               str_view);
+    sequence_state->state()->sequence_stack_profile_tracker().AddString(
+        entry.iid(), str_view);
   }
 
   for (auto it = packet.mappings(); it; ++it) {
     protos::pbzero::Mapping::Decoder entry(*it);
-    StackProfileTracker::SourceMapping src_mapping =
+    SequenceStackProfileTracker::SourceMapping src_mapping =
         ProfilePacketUtils::MakeSourceMapping(entry);
-    sequence_state->state()->stack_profile_tracker().AddMapping(entry.iid(),
-                                                                src_mapping);
+    sequence_state->state()->sequence_stack_profile_tracker().AddMapping(
+        entry.iid(), src_mapping);
   }
 
   for (auto it = packet.frames(); it; ++it) {
     protos::pbzero::Frame::Decoder entry(*it);
-    StackProfileTracker::SourceFrame src_frame =
+    SequenceStackProfileTracker::SourceFrame src_frame =
         ProfilePacketUtils::MakeSourceFrame(entry);
-    sequence_state->state()->stack_profile_tracker().AddFrame(entry.iid(),
-                                                              src_frame);
+    sequence_state->state()->sequence_stack_profile_tracker().AddFrame(
+        entry.iid(), src_frame);
   }
 
   for (auto it = packet.callstacks(); it; ++it) {
     protos::pbzero::Callstack::Decoder entry(*it);
-    StackProfileTracker::SourceCallstack src_callstack =
+    SequenceStackProfileTracker::SourceCallstack src_callstack =
         ProfilePacketUtils::MakeSourceCallstack(entry);
-    sequence_state->state()->stack_profile_tracker().AddCallstack(
+    sequence_state->state()->sequence_stack_profile_tracker().AddCallstack(
         entry.iid(), src_callstack);
   }
 
@@ -320,14 +334,15 @@
       }
       src_allocation.timestamp = timestamp;
       src_allocation.callstack_id = sample.callstack_id();
-      if (sample.self_max()) {
+      if (sample.has_self_max()) {
         src_allocation.self_allocated = sample.self_max();
+        src_allocation.alloc_count = sample.self_max_count();
       } else {
         src_allocation.self_allocated = sample.self_allocated();
         src_allocation.self_freed = sample.self_freed();
+        src_allocation.alloc_count = sample.alloc_count();
+        src_allocation.free_count = sample.free_count();
       }
-      src_allocation.alloc_count = sample.alloc_count();
-      src_allocation.free_count = sample.free_count();
 
       context_->heap_profile_tracker->StoreAllocation(seq_id, src_allocation);
     }
@@ -336,11 +351,72 @@
     PERFETTO_CHECK(sequence_state);
     ProfilePacketInternLookup intern_lookup(sequence_state);
     context_->heap_profile_tracker->FinalizeProfile(
-        seq_id, &sequence_state->state()->stack_profile_tracker(),
+        seq_id, &sequence_state->state()->sequence_stack_profile_tracker(),
         &intern_lookup);
   }
 }
 
+void ProtoTraceParser::ParseDeobfuscationMapping(int64_t,
+                                                 PacketSequenceStateGeneration*,
+                                                 uint32_t /* seq_id */,
+                                                 ConstBytes blob) {
+  protos::pbzero::DeobfuscationMapping::Decoder deobfuscation_mapping(
+      blob.data, blob.size);
+  if (deobfuscation_mapping.package_name().size == 0)
+    return;
+
+  auto opt_package_name_id = context_->storage->string_pool().GetId(
+      deobfuscation_mapping.package_name());
+  auto opt_memfd_id = context_->storage->string_pool().GetId("memfd");
+  if (!opt_package_name_id && !opt_memfd_id)
+    return;
+
+  for (auto class_it = deobfuscation_mapping.obfuscated_classes(); class_it;
+       ++class_it) {
+    protos::pbzero::ObfuscatedClass::Decoder cls(*class_it);
+    for (auto member_it = cls.obfuscated_methods(); member_it; ++member_it) {
+      protos::pbzero::ObfuscatedMember::Decoder member(*member_it);
+      std::string merged_obfuscated = cls.obfuscated_name().ToStdString() +
+                                      "." +
+                                      member.obfuscated_name().ToStdString();
+      auto merged_obfuscated_id = context_->storage->string_pool().GetId(
+          base::StringView(merged_obfuscated));
+      if (!merged_obfuscated_id)
+        continue;
+      std::string merged_deobfuscated =
+          FullyQualifiedDeobfuscatedName(cls, member);
+
+      std::vector<tables::StackProfileFrameTable::Id> frames;
+      if (opt_package_name_id) {
+        const std::vector<tables::StackProfileFrameTable::Id>* pkg_frames =
+            context_->global_stack_profile_tracker->JavaFramesForName(
+                {*merged_obfuscated_id, *opt_package_name_id});
+        if (pkg_frames) {
+          frames.insert(frames.end(), pkg_frames->begin(), pkg_frames->end());
+        }
+      }
+      if (opt_memfd_id) {
+        const std::vector<tables::StackProfileFrameTable::Id>* memfd_frames =
+            context_->global_stack_profile_tracker->JavaFramesForName(
+                {*merged_obfuscated_id, *opt_memfd_id});
+        if (memfd_frames) {
+          frames.insert(frames.end(), memfd_frames->begin(),
+                        memfd_frames->end());
+        }
+      }
+
+      for (tables::StackProfileFrameTable::Id frame_id : frames) {
+        auto* frames_tbl =
+            context_->storage->mutable_stack_profile_frame_table();
+        frames_tbl->mutable_deobfuscated_name()->Set(
+            *frames_tbl->id().IndexOf(frame_id),
+            context_->storage->InternString(
+                base::StringView(merged_deobfuscated)));
+      }
+    }
+  }
+}
+
 void ProtoTraceParser::ParsePerfSample(
     int64_t ts,
     PacketSequenceStateGeneration* sequence_state,
@@ -374,8 +450,8 @@
   }
 
   // Proper sample, though possibly with an incomplete stack unwind.
-  StackProfileTracker& stack_tracker =
-      sequence_state->state()->stack_profile_tracker();
+  SequenceStackProfileTracker& stack_tracker =
+      sequence_state->state()->sequence_stack_profile_tracker();
   ProfilePacketInternLookup intern_lookup(sequence_state);
 
   uint64_t callstack_iid = sample.callstack_iid();
@@ -656,6 +732,17 @@
     context_->metadata_tracker->SetMetadata(metadata::unique_session_name,
                                             Variadic::String(id));
   }
+
+  DescriptorPool pool;
+  pool.AddFromFileDescriptorSet(kConfigDescriptor.data(),
+                                kConfigDescriptor.size());
+
+  std::string text = protozero_to_text::ProtozeroToText(
+      pool, ".perfetto.protos.TraceConfig", blob,
+      protozero_to_text::kIncludeNewLines);
+  StringId id = context_->storage->InternString(base::StringView(text));
+  context_->metadata_tracker->SetMetadata(metadata::trace_config_pbtxt,
+                                          Variadic::String(id));
 }
 
 void ProtoTraceParser::ParseModuleSymbols(ConstBytes blob) {
@@ -670,7 +757,7 @@
         module_symbols.build_id().data, module_symbols.build_id().size)));
   }
 
-  auto mapping_ids = context_->storage->FindMappingRow(
+  auto mapping_ids = context_->global_stack_profile_tracker->FindMappingRow(
       context_->storage->InternString(module_symbols.path()), build_id);
   if (mapping_ids.empty()) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
@@ -695,8 +782,9 @@
     }
     bool frame_found = false;
     for (MappingId mapping_id : mapping_ids) {
-      std::vector<FrameId> frame_ids = context_->storage->FindFrameIds(
-          mapping_id, address_symbols.address());
+      std::vector<FrameId> frame_ids =
+          context_->global_stack_profile_tracker->FindFrameIds(
+              mapping_id, address_symbols.address());
 
       for (const FrameId frame_id : frame_ids) {
         auto* frames = context_->storage->mutable_stack_profile_frame_table();
@@ -710,7 +798,6 @@
       context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
       continue;
     }
-
   }
 }
 
@@ -745,7 +832,18 @@
         {upid, ts, context_->storage->InternString(e.path()),
          static_cast<int64_t>(e.size_kb()),
          static_cast<int64_t>(e.private_dirty_kb()),
-         static_cast<int64_t>(e.swap_kb())});
+         static_cast<int64_t>(e.swap_kb()),
+         context_->storage->InternString(e.file_name()),
+         static_cast<int64_t>(e.start_address()),
+         static_cast<int64_t>(e.module_timestamp()),
+         context_->storage->InternString(e.module_debugid()),
+         context_->storage->InternString(e.module_debug_path()),
+         static_cast<int32_t>(e.protection_flags()),
+         static_cast<int64_t>(e.private_clean_resident_kb()),
+         static_cast<int64_t>(e.shared_dirty_resident_kb()),
+         static_cast<int64_t>(e.shared_clean_resident_kb()),
+         static_cast<int64_t>(e.locked_kb()),
+         static_cast<int64_t>(e.proportional_resident_kb())});
   }
 }
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.h b/src/trace_processor/importers/proto/proto_trace_parser.h
index 995c5b3..18600fa 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.h
+++ b/src/trace_processor/importers/proto/proto_trace_parser.h
@@ -57,8 +57,8 @@
                          TimestampedTracePiece) override;
 
   void ParseTracePacketImpl(int64_t ts,
-                            TimestampedTracePiece,
-                            const TracePacketData*,
+                            const TimestampedTracePiece&,
+                            PacketSequenceStateGeneration*,
                             const protos::pbzero::TracePacket_Decoder&);
 
   void ParseTraceStats(ConstBytes);
@@ -66,6 +66,10 @@
                           PacketSequenceStateGeneration*,
                           uint32_t seq_id,
                           ConstBytes);
+  void ParseDeobfuscationMapping(int64_t ts,
+                                 PacketSequenceStateGeneration*,
+                                 uint32_t seq_id,
+                                 ConstBytes);
   void ParsePerfSample(int64_t ts, PacketSequenceStateGeneration*, ConstBytes);
   void ParseChromeBenchmarkMetadata(ConstBytes);
   void ParseChromeEvents(int64_t ts, ConstBytes);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index a6be8b6..462c451 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_view.h"
@@ -23,6 +23,7 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
@@ -39,6 +40,7 @@
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
 #include "protos/perfetto/common/sys_stats_counters.pbzero.h"
+#include "protos/perfetto/config/trace_config.pbzero.h"
 #include "protos/perfetto/trace/android/packages_list.pbzero.h"
 #include "protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.h"
 #include "protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
@@ -75,6 +77,7 @@
 using ::testing::AtLeast;
 using ::testing::ElementsAreArray;
 using ::testing::Eq;
+using ::testing::HasSubstr;
 using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::InvokeArgument;
@@ -204,6 +207,19 @@
                                         SetArgsCallback args_callback));
 };
 
+class MockFlowTracker : public FlowTracker {
+ public:
+  MockFlowTracker(TraceProcessorContext* context) : FlowTracker(context) {}
+
+  MOCK_METHOD2(Begin, void(TrackId track_id, FlowId flow_id));
+  MOCK_METHOD2(Step, void(TrackId track_id, FlowId flow_id));
+  MOCK_METHOD4(End,
+               void(TrackId track_id,
+                    FlowId flow_id,
+                    bool bind_enclosing,
+                    bool close_flow));
+};
+
 class ProtoTraceParserTest : public ::testing::Test {
  public:
   ProtoTraceParserTest() {
@@ -211,6 +227,8 @@
     context_.storage.reset(storage_);
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
+    context_.global_stack_profile_tracker.reset(
+        new GlobalStackProfileTracker());
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.metadata_tracker.reset(new MetadataTracker(&context_));
     event_ = new MockEventTracker(&context_);
@@ -221,6 +239,8 @@
     context_.process_tracker.reset(process_);
     slice_ = new MockSliceTracker(&context_);
     context_.slice_tracker.reset(slice_);
+    flow_ = new MockFlowTracker(&context_);
+    context_.flow_tracker.reset(flow_);
     clock_ = new ClockTracker(&context_);
     context_.clock_tracker.reset(clock_);
     context_.sorter.reset(new TraceSorter(CreateParser(), 0 /*window size*/));
@@ -230,21 +250,16 @@
     RegisterAdditionalModules(&context_);
   }
 
-  void ResetTraceBuffers() {
-    heap_buf_.reset(new protozero::ScatteredHeapBuffer());
-    stream_writer_.reset(new protozero::ScatteredStreamWriter(heap_buf_.get()));
-    heap_buf_->set_writer(stream_writer_.get());
-    trace_.Reset(stream_writer_.get());
-  }
+  void ResetTraceBuffers() { trace_.Reset(); }
 
   void SetUp() override { ResetTraceBuffers(); }
 
   util::Status Tokenize() {
-    trace_.Finalize();
-    std::vector<uint8_t> trace_bytes = heap_buf_->StitchSlices();
+    trace_->Finalize();
+    std::vector<uint8_t> trace_bytes = trace_.SerializeAsArray();
     std::unique_ptr<uint8_t[]> raw_trace(new uint8_t[trace_bytes.size()]);
     memcpy(raw_trace.get(), trace_bytes.data(), trace_bytes.size());
-    context_.chunk_reader.reset(new ProtoTraceTokenizer(&context_));
+    context_.chunk_reader.reset(new ProtoTraceReader(&context_));
     auto status =
         context_.chunk_reader->Parse(std::move(raw_trace), trace_bytes.size());
 
@@ -273,14 +288,13 @@
     return std::unique_ptr<TraceParser>(new ProtoTraceParser(&context_));
   }
 
-  std::unique_ptr<protozero::ScatteredHeapBuffer> heap_buf_;
-  std::unique_ptr<protozero::ScatteredStreamWriter> stream_writer_;
-  protos::pbzero::Trace trace_;
+  protozero::HeapBuffered<protos::pbzero::Trace> trace_;
   TraceProcessorContext context_;
   MockEventTracker* event_;
   MockSchedEventTracker* sched_;
   MockProcessTracker* process_;
   MockSliceTracker* slice_;
+  MockFlowTracker* flow_;
   ClockTracker* clock_;
   TraceStorage* storage_;
 };
@@ -288,7 +302,7 @@
 // TODO(eseckler): Refactor these into a new file for ftrace tests.
 
 TEST_F(ProtoTraceParserTest, LoadSingleEvent) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
 
   auto* event = bundle->add_event();
@@ -313,7 +327,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadEventsIntoRaw) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
 
   // This event is unknown and will only appear in
@@ -365,7 +379,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadGenericFtrace) {
-  auto* packet = trace_.add_packet();
+  auto* packet = trace_->add_packet();
   packet->set_timestamp(100);
 
   auto* bundle = packet->set_ftrace_events();
@@ -418,7 +432,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadMultipleEvents) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
 
   auto* event = bundle->add_event();
@@ -461,7 +475,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadMultiplePackets) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
 
   auto* event = bundle->add_event();
@@ -479,7 +493,7 @@
   sched_switch->set_next_pid(100);
   sched_switch->set_next_prio(1024);
 
-  bundle = trace_.add_packet()->set_ftrace_events();
+  bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
 
   event = bundle->add_event();
@@ -506,7 +520,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, RepeatedLoadSinglePacket) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
   auto* event = bundle->add_event();
   event->set_timestamp(1000);
@@ -526,7 +540,7 @@
                               32, 100, base::StringView(kProcName1), 1024));
   Tokenize();
 
-  bundle = trace_.add_packet()->set_ftrace_events();
+  bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(10);
   event = bundle->add_event();
   event->set_timestamp(1001);
@@ -547,7 +561,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadCpuFreq) {
-  auto* bundle = trace_.add_packet()->set_ftrace_events();
+  auto* bundle = trace_->add_packet()->set_ftrace_events();
   bundle->set_cpu(12);
   auto* event = bundle->add_event();
   event->set_timestamp(1000);
@@ -563,7 +577,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadMemInfo) {
-  auto* packet = trace_.add_packet();
+  auto* packet = trace_->add_packet();
   uint64_t ts = 1000;
   packet->set_timestamp(ts);
   auto* bundle = packet->set_sys_stats();
@@ -580,7 +594,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadVmStats) {
-  auto* packet = trace_.add_packet();
+  auto* packet = trace_->add_packet();
   uint64_t ts = 1000;
   packet->set_timestamp(ts);
   auto* bundle = packet->set_sys_stats();
@@ -597,7 +611,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadProcessPacket) {
-  auto* tree = trace_.add_packet()->set_process_tree();
+  auto* tree = trace_->add_packet()->set_process_tree();
   auto* process = tree->add_processes();
   static const char kProcName1[] = "proc1";
 
@@ -612,7 +626,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadProcessPacket_FirstCmdline) {
-  auto* tree = trace_.add_packet()->set_process_tree();
+  auto* tree = trace_->add_packet()->set_process_tree();
   auto* process = tree->add_processes();
   static const char kProcName1[] = "proc1";
   static const char kProcName2[] = "proc2";
@@ -629,7 +643,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, LoadThreadPacket) {
-  auto* tree = trace_.add_packet()->set_process_tree();
+  auto* tree = trace_->add_packet()->set_process_tree();
   auto* thread = tree->add_threads();
   thread->set_tid(1);
   thread->set_tgid(2);
@@ -642,7 +656,7 @@
   context_.sorter.reset(new TraceSorter(
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* process_desc = packet->set_process_descriptor();
@@ -650,7 +664,7 @@
     process_desc->set_process_name("OldProcessName");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* process_desc = packet->set_process_descriptor();
@@ -658,7 +672,7 @@
     process_desc->set_process_name("NewProcessName");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_incremental_state_cleared(true);
     auto* process_desc = packet->set_process_descriptor();
@@ -687,7 +701,7 @@
   context_.sorter.reset(new TraceSorter(
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -698,7 +712,7 @@
     thread_desc->set_thread_name("OldThreadName");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -709,7 +723,7 @@
     thread_desc->set_thread_name("NewThreadName");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -744,7 +758,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -754,7 +768,7 @@
     thread_desc->set_reference_thread_time_us(2000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -765,7 +779,7 @@
     legacy_event->set_phase('B');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1020.
@@ -776,7 +790,7 @@
     legacy_event->set_phase('E');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1005);
@@ -838,7 +852,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -848,7 +862,7 @@
     thread_desc->set_reference_thread_time_us(2000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -859,7 +873,7 @@
     legacy_event->set_name_iid(1);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1020.
@@ -870,7 +884,7 @@
     legacy_event->set_name_iid(1);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1015);
@@ -929,7 +943,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -940,7 +954,7 @@
     thread_desc->set_reference_thread_instruction_count(3000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);              // absolute: 1010.
@@ -960,7 +974,7 @@
     ev1->set_name("ev1");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1040);
@@ -972,7 +986,7 @@
     legacy_event->set_phase('I');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1050);
@@ -984,7 +998,7 @@
         protos::pbzero::TrackEvent::LegacyEvent::SCOPE_PROCESS);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);              // absolute: 1020.
@@ -996,7 +1010,7 @@
     legacy_event->set_phase('E');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1005);
@@ -1011,9 +1025,8 @@
     legacy_event->set_thread_duration_us(12);        // absolute end: 2015.
     legacy_event->set_thread_instruction_delta(50);  // absolute end: 3060.
     legacy_event->set_bind_id(9999);
-    legacy_event->set_bind_to_enclosing(true);
     legacy_event->set_flow_direction(
-        protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT);
+        protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT);
 
     auto* interned_data = packet->set_interned_data();
     auto cat2 = interned_data->add_event_categories();
@@ -1027,6 +1040,37 @@
     ev2->set_name("ev2");
   }
 
+  {
+    auto* packet = trace_->add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_absolute_us(1005);
+    event->add_category_iids(2);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(4);
+    legacy_event->set_phase('t');
+    legacy_event->set_unscoped_id(220);
+  }
+
+  {
+    auto* packet = trace_->add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_absolute_us(1005);
+    event->add_category_iids(2);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(4);
+    legacy_event->set_phase('f');
+    legacy_event->set_unscoped_id(330);
+    legacy_event->set_bind_to_enclosing(false);
+  }
+
   Tokenize();
 
   EXPECT_CALL(*process_, UpdateThread(16, 15))
@@ -1056,9 +1100,12 @@
                                    thread_instruction_count_track));
   EXPECT_CALL(*slice_, Scoped(1005000, thread_1_track, cat_2_3, ev_2, 23000, _))
       .WillOnce(DoAll(InvokeArgument<5>(&inserter), Return(0u)));
-  EXPECT_CALL(inserter, AddArg(_, _, Variadic::UnsignedInteger(9999u), _));
-  EXPECT_CALL(inserter, AddArg(_, _, Variadic::Boolean(true), _));
-  EXPECT_CALL(inserter, AddArg(_, _, _, _));
+
+  EXPECT_CALL(*flow_, Begin(_, _));
+
+  EXPECT_CALL(*flow_, Step(_, _));
+
+  EXPECT_CALL(*flow_, End(_, _, false, false));
 
   EXPECT_CALL(*event_, PushCounter(1010000, testing::DoubleEq(2005000),
                                    thread_time_track));
@@ -1111,7 +1158,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -1122,7 +1169,7 @@
     thread_desc->set_reference_thread_instruction_count(3000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);              // absolute: 1010.
@@ -1144,7 +1191,7 @@
     ev1->set_name("ev1");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);              // absolute: 1020.
@@ -1158,7 +1205,7 @@
     legacy_event->set_use_async_tts(true);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1015);
@@ -1175,7 +1222,7 @@
   }
   {
     // Different category but same global_id -> separate track.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1018);
@@ -1191,7 +1238,7 @@
     cat2->set_name("cat2");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_absolute_us(1030);
@@ -1268,7 +1315,7 @@
 
   // Sequence 1.
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     packet->set_timestamp(1000000);
@@ -1280,7 +1327,7 @@
     thread_desc->set_tid(16);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_timestamp(1000000);
     auto* track_desc = packet->set_track_descriptor();
@@ -1289,7 +1336,7 @@
   }
   {
     // Async event started on "Async track 1".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_timestamp(1010000);
     auto* event = packet->set_track_event();
@@ -1312,7 +1359,7 @@
   }
   {
     // Instant event on "Thread track 1".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_timestamp(1015000);
     auto* event = packet->set_track_event();
@@ -1333,7 +1380,7 @@
 
   // Sequence 2.
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_incremental_state_cleared(true);
     packet->set_timestamp(1000000);
@@ -1346,7 +1393,7 @@
   }
   {
     // Async event completed on "Async track 1".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_timestamp(1020000);
     auto* event = packet->set_track_event();
@@ -1359,7 +1406,7 @@
   }
   {
     // Instant event on "Thread track 2".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_timestamp(1016000);
     auto* event = packet->set_track_event();
@@ -1404,13 +1451,13 @@
       .WillOnce(Return(0u));
 
   EXPECT_CALL(*event_,
-              PushCounter(1015000, testing::DoubleEq(2007000), TrackId{4}));
+              PushCounter(1015000, testing::DoubleEq(2007000), TrackId{3}));
   EXPECT_CALL(*slice_, Scoped(1015000, TrackId{0}, cat_2, ev_2, 0, _))
       .WillOnce(Return(1u));
 
   EXPECT_CALL(*event_,
-              PushCounter(1016000, testing::DoubleEq(2008000), TrackId{5}));
-  EXPECT_CALL(*slice_, Scoped(1016000, TrackId{3}, cat_3, ev_3, 0, _))
+              PushCounter(1016000, testing::DoubleEq(2008000), TrackId{4}));
+  EXPECT_CALL(*slice_, Scoped(1016000, TrackId{2}, cat_3, ev_3, 0, _))
       .WillOnce(Return(2u));
 
   EXPECT_CALL(*slice_,
@@ -1422,11 +1469,10 @@
   // First track is "Thread track 1"; second is "Async track 1", third is global
   // default track (parent of async track), fourth is "Thread track 2", fifth &
   // sixth are thread time tracks for thread 1 and 2.
-  EXPECT_EQ(storage_->track_table().row_count(), 6u);
+  EXPECT_EQ(storage_->track_table().row_count(), 5u);
   EXPECT_EQ(storage_->track_table().name().GetString(0), "Thread track 1");
   EXPECT_EQ(storage_->track_table().name().GetString(1), "Async track 1");
-  EXPECT_EQ(storage_->track_table().name().GetString(2), "Default Track");
-  EXPECT_EQ(storage_->track_table().name().GetString(3), "Thread track 2");
+  EXPECT_EQ(storage_->track_table().name().GetString(2), "Thread track 2");
   EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
   EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
   EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
@@ -1461,7 +1507,7 @@
   // in the order they appear here, but then resorted before parsing to appear
   // after the events below.
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     packet->set_timestamp(3000);
@@ -1479,7 +1525,7 @@
     track_event_defaults->add_extra_counter_track_uuids(10);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_timestamp(3000);
     auto* track_desc = packet->set_track_descriptor();
@@ -1496,7 +1542,7 @@
     // counter values should still be imported as counter values and as args for
     // JSON export. Should appear on default track "t1" with
     // extra_counter_values for "c1".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_sequence_flags(
         protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
@@ -1509,7 +1555,7 @@
   }
   {
     // End for "ev1".
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_timestamp(1100);
     auto* event = packet->set_track_event();
@@ -1563,7 +1609,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* thread_desc = packet->set_thread_descriptor();
     thread_desc->set_pid(15);
@@ -1574,7 +1620,7 @@
   {
     // Event should be discarded because delta timestamps require valid
     // incremental state + thread descriptor.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -1587,7 +1633,7 @@
   {
     // Event should be discarded because it specifies
     // SEQ_NEEDS_INCREMENTAL_STATE.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_timestamp(2000000);
     packet->set_trusted_packet_sequence_id(1);
     packet->set_sequence_flags(
@@ -1600,7 +1646,7 @@
   {
     // Event should be accepted because it does not specify
     // SEQ_NEEDS_INCREMENTAL_STATE and uses absolute timestamps.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_timestamp(2100000);
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
@@ -1626,7 +1672,7 @@
   {
     // Event should be discarded because it specifies delta timestamps and no
     // thread descriptor was seen yet.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* event = packet->set_track_event();
@@ -1640,7 +1686,7 @@
   {
     // Events that specify SEQ_NEEDS_INCREMENTAL_STATE should be accepted even
     // if there's no valid thread descriptor.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_timestamp(2000000);
     packet->set_trusted_packet_sequence_id(1);
     packet->set_sequence_flags(
@@ -1666,7 +1712,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -1675,7 +1721,7 @@
     thread_desc->set_reference_timestamp_us(1000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -1686,7 +1732,7 @@
   }
   {
     // Event should be dropped because data loss occurred before.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_previous_packet_dropped(true);  // Data loss occurred.
     auto* event = packet->set_track_event();
@@ -1698,7 +1744,7 @@
   }
   {
     // Event should be dropped because incremental state is invalid.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);
@@ -1709,7 +1755,7 @@
   }
   {
     // Event should be dropped because no new thread descriptor was seen yet.
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* event = packet->set_track_event();
@@ -1720,7 +1766,7 @@
     legacy_event->set_phase('E');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* thread_desc = packet->set_thread_descriptor();
     thread_desc->set_pid(15);
@@ -1728,7 +1774,7 @@
     thread_desc->set_reference_timestamp_us(2000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 2010.
@@ -1760,7 +1806,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -1769,7 +1815,7 @@
     thread_desc->set_reference_timestamp_us(1000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -1787,7 +1833,7 @@
     ev1->set_name("ev1");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -1796,7 +1842,7 @@
     thread_desc->set_reference_timestamp_us(995);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1005.
@@ -1814,7 +1860,7 @@
     ev2->set_name("ev2");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1020.
@@ -1824,7 +1870,7 @@
     legacy_event->set_phase('E');
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(2);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1015.
@@ -1871,7 +1917,7 @@
   MockBoundInserter inserter;
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -1880,7 +1926,7 @@
     thread_desc->set_reference_timestamp_us(1000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -1932,7 +1978,7 @@
     an2->set_name("an2");
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1020.
@@ -2072,7 +2118,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -2081,7 +2127,7 @@
     thread_desc->set_reference_timestamp_us(1000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -2138,7 +2184,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -2147,7 +2193,7 @@
     thread_desc->set_reference_timestamp_us(1000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -2215,7 +2261,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -2225,7 +2271,7 @@
     thread_desc->set_reference_thread_time_us(2000);
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);   // absolute: 1010.
@@ -2240,11 +2286,7 @@
     legacy_event->set_thread_duration_us(15);
     legacy_event->set_global_id(99u);
     legacy_event->set_id_scope("scope1");
-    legacy_event->set_use_async_tts('?');
-    legacy_event->set_bind_id(98);
-    legacy_event->set_bind_to_enclosing(true);
-    legacy_event->set_flow_direction(
-        protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT);
+    legacy_event->set_use_async_tts(true);
 
     auto* annotation1 = event->add_debug_annotations();
     annotation1->set_name_iid(1);
@@ -2293,7 +2335,7 @@
   EXPECT_EQ(raw_table.utid()[0], 1u);
   EXPECT_EQ(raw_table.arg_set_id()[0], 1u);
 
-  EXPECT_GE(storage_->arg_table().row_count(), 13u);
+  EXPECT_GE(storage_->arg_table().row_count(), 10u);
 
   EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.category"),
                      Variadic::String(cat_1)));
@@ -2315,13 +2357,6 @@
                      Variadic::UnsignedInteger(99u)));
   EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.id_scope"),
                      Variadic::String(scope_1)));
-  EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.bind_id"),
-                     Variadic::UnsignedInteger(98u)));
-  EXPECT_TRUE(HasArg(1u,
-                     storage_->InternString("legacy_event.bind_to_enclosing"),
-                     Variadic::Boolean(true)));
-  EXPECT_TRUE(HasArg(1u, storage_->InternString("legacy_event.flow_direction"),
-                     Variadic::String(storage_->InternString("inout"))));
   EXPECT_TRUE(HasArg(1u, debug_an_1, Variadic::UnsignedInteger(10u)));
 }
 
@@ -2333,7 +2368,7 @@
                        {protos::pbzero::BUILTIN_CLOCK_MONOTONIC, 1000000}});
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
     auto* thread_desc = packet->set_thread_descriptor();
@@ -2342,7 +2377,7 @@
     thread_desc->set_reference_timestamp_us(1000);  // MONOTONIC.
   }
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* event = packet->set_track_event();
     event->set_timestamp_delta_us(10);  // absolute: 1010 (mon), 10 (boot).
@@ -2375,7 +2410,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_timestamp(1000);
     packet->set_timestamp_clock_id(3);
     packet->set_trusted_packet_sequence_id(1);
@@ -2404,7 +2439,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_timestamp(1000);
     packet->set_timestamp_clock_id(3);
     packet->set_trusted_packet_sequence_id(1);
@@ -2443,7 +2478,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* bundle = packet->set_chrome_events();
     bundle->add_legacy_ftrace_output(kDataPart0);
@@ -2473,7 +2508,7 @@
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* bundle = packet->set_chrome_events();
     auto* user_trace = bundle->add_legacy_json_trace();
@@ -2506,7 +2541,7 @@
   context_.sorter.reset(new TraceSorter(
       CreateParser(), std::numeric_limits<int64_t>::max() /*window size*/));
 
-  auto* metadata = trace_.add_packet()->set_chrome_benchmark_metadata();
+  auto* metadata = trace_->add_packet()->set_chrome_benchmark_metadata();
   metadata->set_benchmark_name(kName);
   metadata->add_story_tags(kTag1);
   metadata->add_story_tags(kTag2);
@@ -2534,7 +2569,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, AndroidPackagesList) {
-  auto* packet = trace_.add_packet();
+  auto* packet = trace_->add_packet();
   auto* pkg_list = packet->set_packages_list();
 
   pkg_list->set_read_error(false);
@@ -2586,7 +2621,7 @@
 }
 
 TEST_F(ProtoTraceParserTest, AndroidPackagesListDuplicate) {
-  auto* packet = trace_.add_packet();
+  auto* packet = trace_->add_packet();
   auto* pkg_list = packet->set_packages_list();
 
   pkg_list->set_read_error(false);
@@ -2632,7 +2667,7 @@
 
 TEST_F(ProtoTraceParserTest, ParseCPUProfileSamplesIntoTable) {
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
 
@@ -2672,7 +2707,7 @@
   }
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
 
     auto* samples = packet->set_streaming_profile_packet();
@@ -2685,7 +2720,7 @@
   }
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     auto* samples = packet->set_streaming_profile_packet();
 
@@ -2728,7 +2763,7 @@
 
 TEST_F(ProtoTraceParserTest, CPUProfileSamplesTimestampsAreClockMonotonic) {
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(0);
 
     // 1000 us monotonic == 10000 us boottime.
@@ -2742,7 +2777,7 @@
   }
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
     packet->set_incremental_state_cleared(true);
 
@@ -2773,7 +2808,7 @@
   }
 
   {
-    auto* packet = trace_.add_packet();
+    auto* packet = trace_->add_packet();
     packet->set_trusted_packet_sequence_id(1);
 
     auto* samples = packet->set_streaming_profile_packet();
@@ -2794,6 +2829,29 @@
   EXPECT_EQ(samples.utid()[0], 1u);
 }
 
+TEST_F(ProtoTraceParserTest, ConfigUuid) {
+  auto* config = trace_->add_packet()->set_trace_config();
+  config->set_trace_uuid_lsb(1);
+  config->set_trace_uuid_msb(2);
+
+  ASSERT_TRUE(Tokenize().ok());
+
+  SqlValue value =
+      context_.metadata_tracker->GetMetadataForTesting(metadata::trace_uuid);
+  EXPECT_STREQ(value.string_value, "00000000-0000-0002-0000-000000000001");
+}
+
+TEST_F(ProtoTraceParserTest, ConfigPbtxt) {
+  auto* config = trace_->add_packet()->set_trace_config();
+  config->add_buffers()->set_size_kb(42);
+
+  ASSERT_TRUE(Tokenize().ok());
+
+  SqlValue value = context_.metadata_tracker->GetMetadataForTesting(
+      metadata::trace_config_pbtxt);
+  EXPECT_THAT(value.string_value, HasSubstr("size_kb: 42"));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.cc b/src/trace_processor/importers/proto/proto_trace_reader.cc
new file mode 100644
index 0000000..e2b1cde
--- /dev/null
+++ b/src/trace_processor/importers/proto/proto_trace_reader.cc
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
+
+#include <string>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/importers/common/clock_tracker.h"
+#include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/gzip/gzip_utils.h"
+#include "src/trace_processor/importers/proto/args_table_utils.h"
+#include "src/trace_processor/importers/proto/metadata_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/proto_incremental_state.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/trace_sorter.h"
+
+#include "protos/perfetto/common/builtin_clock.pbzero.h"
+#include "protos/perfetto/config/trace_config.pbzero.h"
+#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
+#include "protos/perfetto/trace/extension_descriptor.pbzero.h"
+#include "protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h"
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+ProtoTraceReader::ProtoTraceReader(TraceProcessorContext* ctx)
+    : context_(ctx) {}
+ProtoTraceReader::~ProtoTraceReader() = default;
+
+util::Status ProtoTraceReader::Parse(std::unique_ptr<uint8_t[]> owned_buf,
+                                     size_t size) {
+  return tokenizer_.Tokenize(
+      std::move(owned_buf), size,
+      [this](TraceBlobView packet) { return ParsePacket(std::move(packet)); });
+}
+
+util::Status ProtoTraceReader::ParseExtensionDescriptor(ConstBytes descriptor) {
+  protos::pbzero::ExtensionDescriptor::Decoder decoder(descriptor.data,
+                                                       descriptor.size);
+
+  auto extension = decoder.extension_set();
+  return context_->proto_to_args_table_->AddProtoFileDescriptor(extension.data,
+                                                                extension.size);
+}
+
+util::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) {
+  protos::pbzero::TracePacket::Decoder decoder(packet.data(), packet.length());
+  if (PERFETTO_UNLIKELY(decoder.bytes_left())) {
+    return util::ErrStatus(
+        "Failed to parse proto packet fully; the trace is probably corrupt.");
+  }
+
+  // Any compressed packets should have been handled by the tokenizer.
+  PERFETTO_CHECK(!decoder.has_compressed_packets());
+
+  const uint32_t seq_id = decoder.trusted_packet_sequence_id();
+  auto* state = GetIncrementalStateForPacketSequence(seq_id);
+
+  uint32_t sequence_flags = decoder.sequence_flags();
+
+  if (decoder.incremental_state_cleared() ||
+      sequence_flags &
+          protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+    HandleIncrementalStateCleared(decoder);
+  } else if (decoder.previous_packet_dropped()) {
+    HandlePreviousPacketDropped(decoder);
+  }
+
+  // It is important that we parse defaults before parsing other fields such as
+  // the timestamp, since the defaults could affect them.
+  if (decoder.has_trace_packet_defaults()) {
+    auto field = decoder.trace_packet_defaults();
+    const size_t offset = packet.offset_of(field.data);
+    ParseTracePacketDefaults(decoder, packet.slice(offset, field.size));
+  }
+
+  if (decoder.has_interned_data()) {
+    auto field = decoder.interned_data();
+    const size_t offset = packet.offset_of(field.data);
+    ParseInternedData(decoder, packet.slice(offset, field.size));
+  }
+
+  if (decoder.has_clock_snapshot()) {
+    return ParseClockSnapshot(decoder.clock_snapshot(),
+                              decoder.trusted_packet_sequence_id());
+  }
+
+  if (decoder.has_service_event()) {
+    PERFETTO_DCHECK(decoder.has_timestamp());
+    int64_t ts = static_cast<int64_t>(decoder.timestamp());
+    return ParseServiceEvent(ts, decoder.service_event());
+  }
+
+  if (decoder.has_extension_descriptor()) {
+    return ParseExtensionDescriptor(decoder.extension_descriptor());
+  }
+
+  if (decoder.sequence_flags() &
+      protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
+    if (!seq_id) {
+      return util::ErrStatus(
+          "TracePacket specified SEQ_NEEDS_INCREMENTAL_STATE but the "
+          "TraceWriter's sequence_id is zero (the service is "
+          "probably too old)");
+    }
+
+    if (!state->IsIncrementalStateValid()) {
+      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+      return util::OkStatus();
+    }
+  }
+
+  protos::pbzero::TracePacketDefaults::Decoder* defaults =
+      state->current_generation()->GetTracePacketDefaults();
+
+  int64_t timestamp;
+  if (decoder.has_timestamp()) {
+    timestamp = static_cast<int64_t>(decoder.timestamp());
+
+    uint32_t timestamp_clock_id =
+        decoder.has_timestamp_clock_id()
+            ? decoder.timestamp_clock_id()
+            : (defaults ? defaults->timestamp_clock_id() : 0);
+
+    if ((decoder.has_chrome_events() || decoder.has_chrome_metadata()) &&
+        (!timestamp_clock_id ||
+         timestamp_clock_id == protos::pbzero::BUILTIN_CLOCK_MONOTONIC)) {
+      // Chrome event timestamps are in MONOTONIC domain, but may occur in
+      // traces where (a) no clock snapshots exist or (b) no clock_id is
+      // specified for their timestamps. Adjust to trace time if we have a clock
+      // snapshot.
+      // TODO(eseckler): Set timestamp_clock_id and emit ClockSnapshots in
+      // chrome and then remove this.
+      auto trace_ts = context_->clock_tracker->ToTraceTime(
+          protos::pbzero::BUILTIN_CLOCK_MONOTONIC, timestamp);
+      if (trace_ts.has_value())
+        timestamp = trace_ts.value();
+    } else if (timestamp_clock_id) {
+      // If the TracePacket specifies a non-zero clock-id, translate the
+      // timestamp into the trace-time clock domain.
+      ClockTracker::ClockId converted_clock_id = timestamp_clock_id;
+      bool is_seq_scoped =
+          ClockTracker::IsReservedSeqScopedClockId(converted_clock_id);
+      if (is_seq_scoped) {
+        if (!seq_id) {
+          return util::ErrStatus(
+              "TracePacket specified a sequence-local clock id (%" PRIu32
+              ") but the TraceWriter's sequence_id is zero (the service is "
+              "probably too old)",
+              timestamp_clock_id);
+        }
+        converted_clock_id =
+            ClockTracker::SeqScopedClockIdToGlobal(seq_id, timestamp_clock_id);
+      }
+      auto trace_ts =
+          context_->clock_tracker->ToTraceTime(converted_clock_id, timestamp);
+      if (!trace_ts.has_value()) {
+        // ToTraceTime() will increase the |clock_sync_failure| stat on failure.
+        static const char seq_extra_err[] =
+            " Because the clock id is sequence-scoped, the ClockSnapshot must "
+            "be emitted on the same TraceWriter sequence of the packet that "
+            "refers to that clock id.";
+        return util::ErrStatus(
+            "Failed to convert TracePacket's timestamp from clock_id=%" PRIu32
+            " seq_id=%" PRIu32
+            ". This is usually due to the lack of a prior ClockSnapshot "
+            "proto.%s",
+            timestamp_clock_id, seq_id, is_seq_scoped ? seq_extra_err : "");
+      }
+      timestamp = trace_ts.value();
+    }
+  } else {
+    timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());
+  }
+  latest_timestamp_ = std::max(timestamp, latest_timestamp_);
+
+  auto& modules = context_->modules_by_field;
+  for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
+    if (modules[field_id] && decoder.Get(field_id).valid()) {
+      ModuleResult res = modules[field_id]->TokenizePacket(
+          decoder, &packet, timestamp, state, field_id);
+      if (!res.ignored())
+        return res.ToStatus();
+    }
+  }
+
+  // If we're not forcing a full sort and this is a write_into_file trace, then
+  // use flush_period_ms as an indiciator for how big the sliding window for the
+  // sorter should be.
+  if (!context_->config.force_full_sort && decoder.has_trace_config()) {
+    auto config = decoder.trace_config();
+    protos::pbzero::TraceConfig::Decoder trace_config(config.data, config.size);
+
+    if (trace_config.write_into_file()) {
+      int64_t window_size_ns;
+      if (trace_config.has_flush_period_ms() &&
+          trace_config.flush_period_ms() > 0) {
+        // We use 2x the flush period as a margin of error to allow for any
+        // late flush responses to still be sorted correctly.
+        window_size_ns = static_cast<int64_t>(trace_config.flush_period_ms()) *
+                         2 * 1000 * 1000;
+      } else {
+        constexpr uint64_t kDefaultWindowNs =
+            180 * 1000 * 1000 * 1000ULL;  // 3 minutes.
+        PERFETTO_ELOG(
+            "It is strongly recommended to have flush_period_ms set when "
+            "write_into_file is turned on. You will likely have many dropped "
+            "events because of inability to sort the events correctly.");
+        window_size_ns = static_cast<int64_t>(kDefaultWindowNs);
+      }
+      context_->sorter->SetWindowSizeNs(window_size_ns);
+    }
+  }
+
+  // Use parent data and length because we want to parse this again
+  // later to get the exact type of the packet.
+  context_->sorter->PushTracePacket(timestamp, state, std::move(packet));
+
+  return util::OkStatus();
+}
+
+void ProtoTraceReader::HandleIncrementalStateCleared(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG(
+        "incremental_state_cleared without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    return;
+  }
+  GetIncrementalStateForPacketSequence(
+      packet_decoder.trusted_packet_sequence_id())
+      ->OnIncrementalStateCleared();
+  for (auto& module : context_->modules) {
+    module->OnIncrementalStateCleared(
+        packet_decoder.trusted_packet_sequence_id());
+  }
+}
+
+void ProtoTraceReader::HandlePreviousPacketDropped(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG("previous_packet_dropped without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    return;
+  }
+  GetIncrementalStateForPacketSequence(
+      packet_decoder.trusted_packet_sequence_id())
+      ->OnPacketLoss();
+}
+
+void ProtoTraceReader::ParseTracePacketDefaults(
+    const protos::pbzero::TracePacket_Decoder& packet_decoder,
+    TraceBlobView trace_packet_defaults) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG(
+        "TracePacketDefaults packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    return;
+  }
+
+  auto* state = GetIncrementalStateForPacketSequence(
+      packet_decoder.trusted_packet_sequence_id());
+  state->UpdateTracePacketDefaults(std::move(trace_packet_defaults));
+}
+
+void ProtoTraceReader::ParseInternedData(
+    const protos::pbzero::TracePacket::Decoder& packet_decoder,
+    TraceBlobView interned_data) {
+  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
+    PERFETTO_ELOG("InternedData packet without trusted_packet_sequence_id");
+    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
+    return;
+  }
+
+  auto* state = GetIncrementalStateForPacketSequence(
+      packet_decoder.trusted_packet_sequence_id());
+
+  // Don't parse interned data entries until incremental state is valid, because
+  // they could otherwise be associated with the wrong generation in the state.
+  if (!state->IsIncrementalStateValid()) {
+    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
+    return;
+  }
+
+  // Store references to interned data submessages into the sequence's state.
+  protozero::ProtoDecoder decoder(interned_data.data(), interned_data.length());
+  for (protozero::Field f = decoder.ReadField(); f.valid();
+       f = decoder.ReadField()) {
+    auto bytes = f.as_bytes();
+    auto offset = interned_data.offset_of(bytes.data);
+    state->InternMessage(f.id(), interned_data.slice(offset, bytes.size));
+  }
+}
+
+util::Status ProtoTraceReader::ParseClockSnapshot(ConstBytes blob,
+                                                  uint32_t seq_id) {
+  std::vector<ClockTracker::ClockValue> clocks;
+  protos::pbzero::ClockSnapshot::Decoder evt(blob.data, blob.size);
+  if (evt.primary_trace_clock()) {
+    context_->clock_tracker->SetTraceTimeClock(
+        static_cast<ClockTracker::ClockId>(evt.primary_trace_clock()));
+  }
+  for (auto it = evt.clocks(); it; ++it) {
+    protos::pbzero::ClockSnapshot::Clock::Decoder clk(*it);
+    ClockTracker::ClockId clock_id = clk.clock_id();
+    if (ClockTracker::IsReservedSeqScopedClockId(clk.clock_id())) {
+      if (!seq_id) {
+        return util::ErrStatus(
+            "ClockSnapshot packet is specifying a sequence-scoped clock id "
+            "(%" PRIu64 ") but the TracePacket sequence_id is zero",
+            clock_id);
+      }
+      clock_id = ClockTracker::SeqScopedClockIdToGlobal(seq_id, clk.clock_id());
+    }
+    int64_t unit_multiplier_ns =
+        clk.unit_multiplier_ns()
+            ? static_cast<int64_t>(clk.unit_multiplier_ns())
+            : 1;
+    clocks.emplace_back(clock_id, clk.timestamp(), unit_multiplier_ns,
+                        clk.is_incremental());
+  }
+  context_->clock_tracker->AddSnapshot(clocks);
+  return util::OkStatus();
+}
+
+util::Status ProtoTraceReader::ParseServiceEvent(int64_t ts, ConstBytes blob) {
+  protos::pbzero::TracingServiceEvent::Decoder tse(blob);
+  if (tse.tracing_started()) {
+    context_->metadata_tracker->SetMetadata(metadata::tracing_started_ns,
+                                            Variadic::Integer(ts));
+  }
+  if (tse.tracing_disabled()) {
+    context_->metadata_tracker->SetMetadata(metadata::tracing_disabled_ns,
+                                            Variadic::Integer(ts));
+  }
+  if (tse.all_data_sources_started()) {
+    context_->metadata_tracker->SetMetadata(
+        metadata::all_data_source_started_ns, Variadic::Integer(ts));
+  }
+  return util::OkStatus();
+}
+
+void ProtoTraceReader::NotifyEndOfFile() {}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
new file mode 100644
index 0000000..4091f1a
--- /dev/null
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_READER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_READER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "src/trace_processor/chunked_trace_reader.h"
+#include "src/trace_processor/importers/proto/proto_incremental_state.h"
+#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/trace_blob_view.h"
+
+namespace protozero {
+struct ConstBytes;
+}
+
+namespace perfetto {
+
+namespace protos {
+namespace pbzero {
+class TracePacket_Decoder;
+}  // namespace pbzero
+}  // namespace protos
+
+namespace trace_processor {
+
+class PacketSequenceState;
+class TraceProcessorContext;
+class TraceSorter;
+class TraceStorage;
+
+// Implementation of ChunkedTraceReader for proto traces. Tokenizes a proto
+// trace into packets, handles parsing of any packets which need to be
+// handled in trace-order and passes the remainder to TraceSorter to sort
+// into timestamp order.
+class ProtoTraceReader : public ChunkedTraceReader {
+ public:
+  // |reader| is the abstract method of getting chunks of size |chunk_size_b|
+  // from a trace file with these chunks parsed into |trace|.
+  explicit ProtoTraceReader(TraceProcessorContext*);
+  ~ProtoTraceReader() override;
+
+  // ChunkedTraceReader implementation.
+  util::Status Parse(std::unique_ptr<uint8_t[]>, size_t size) override;
+  void NotifyEndOfFile() override;
+
+ private:
+  using ConstBytes = protozero::ConstBytes;
+  util::Status ParsePacket(TraceBlobView);
+  util::Status ParseServiceEvent(int64_t ts, ConstBytes);
+  util::Status ParseClockSnapshot(ConstBytes blob, uint32_t seq_id);
+  void HandleIncrementalStateCleared(
+      const protos::pbzero::TracePacket_Decoder&);
+  void HandlePreviousPacketDropped(const protos::pbzero::TracePacket_Decoder&);
+  void ParseTracePacketDefaults(const protos::pbzero::TracePacket_Decoder&,
+                                TraceBlobView trace_packet_defaults);
+  void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
+                         TraceBlobView interned_data);
+  PacketSequenceState* GetIncrementalStateForPacketSequence(
+      uint32_t sequence_id) {
+    if (!incremental_state)
+      incremental_state.reset(new ProtoIncrementalState(context_));
+    return incremental_state->GetOrCreateStateForPacketSequence(sequence_id);
+  }
+  util::Status ParseExtensionDescriptor(ConstBytes descriptor);
+
+  TraceProcessorContext* context_;
+
+  ProtoTraceTokenizer tokenizer_;
+
+  // Temporary. Currently trace packets do not have a timestamp, so the
+  // timestamp given is latest_timestamp_.
+  int64_t latest_timestamp_ = 0;
+
+  // Stores incremental state and references to interned data, e.g. for track
+  // event protos.
+  std::unique_ptr<ProtoIncrementalState> incremental_state;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_READER_H_
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
index 7f41b9d..fdc7bcd 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -16,50 +16,15 @@
 
 #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
 
-#include <string>
-
-#include "perfetto/base/build_config.h"
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/optional.h"
-#include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/protozero/proto_decoder.h"
-#include "perfetto/protozero/proto_utils.h"
-#include "perfetto/trace_processor/status.h"
-#include "src/trace_processor/importers/common/clock_tracker.h"
-#include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/importers/ftrace/ftrace_module.h"
-#include "src/trace_processor/importers/gzip/gzip_utils.h"
-#include "src/trace_processor/importers/proto/args_table_utils.h"
-#include "src/trace_processor/importers/proto/metadata_tracker.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/proto_incremental_state.h"
-#include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/trace_sorter.h"
-
-#include "protos/perfetto/common/builtin_clock.pbzero.h"
-#include "protos/perfetto/config/trace_config.pbzero.h"
-#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
-#include "protos/perfetto/trace/extension_descriptor.pbzero.h"
-#include "protos/perfetto/trace/perfetto/tracing_service_event.pbzero.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
-#include "protos/perfetto/trace/trace.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-using protozero::proto_utils::MakeTagLengthDelimited;
-using protozero::proto_utils::ParseVarInt;
+ProtoTraceTokenizer::ProtoTraceTokenizer() = default;
 
-namespace {
-
-constexpr uint8_t kTracePacketTag =
-    MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
-
-TraceBlobView Decompress(GzipDecompressor* decompressor, TraceBlobView input) {
+util::Status ProtoTraceTokenizer::Decompress(TraceBlobView input,
+                                             TraceBlobView* output) {
   PERFETTO_DCHECK(gzip::IsGzipSupported());
 
   uint8_t out[4096];
@@ -68,457 +33,27 @@
   data.reserve(input.length());
 
   // Ensure that the decompressor is able to cope with a new stream of data.
-  decompressor->Reset();
-  decompressor->SetInput(input.data(), input.length());
+  decompressor_.Reset();
+  decompressor_.SetInput(input.data(), input.length());
 
   using ResultCode = GzipDecompressor::ResultCode;
   for (auto ret = ResultCode::kOk; ret != ResultCode::kEof;) {
-    auto res = decompressor->Decompress(out, base::ArraySize(out));
+    auto res = decompressor_.Decompress(out, base::ArraySize(out));
     ret = res.ret;
     if (ret == ResultCode::kError || ret == ResultCode::kNoProgress ||
-        ret == ResultCode::kNeedsMoreInput)
-      return TraceBlobView(nullptr, 0, 0);
+        ret == ResultCode::kNeedsMoreInput) {
+      return util::ErrStatus("Failed to decompress (error code: %d)",
+                             static_cast<int>(ret));
+    }
 
     data.insert(data.end(), out, out + res.bytes_written);
   }
 
-  std::unique_ptr<uint8_t[]> output(new uint8_t[data.size()]);
-  memcpy(output.get(), data.data(), data.size());
-  return TraceBlobView(std::move(output), 0, data.size());
-}
-
-}  // namespace
-
-ProtoTraceTokenizer::ProtoTraceTokenizer(TraceProcessorContext* ctx)
-    : context_(ctx) {}
-ProtoTraceTokenizer::~ProtoTraceTokenizer() = default;
-
-util::Status ProtoTraceTokenizer::Parse(std::unique_ptr<uint8_t[]> owned_buf,
-                                        size_t size) {
-  uint8_t* data = &owned_buf[0];
-  if (!partial_buf_.empty()) {
-    // It takes ~5 bytes for a proto preamble + the varint size.
-    const size_t kHeaderBytes = 5;
-    if (PERFETTO_UNLIKELY(partial_buf_.size() < kHeaderBytes)) {
-      size_t missing_len = std::min(kHeaderBytes - partial_buf_.size(), size);
-      partial_buf_.insert(partial_buf_.end(), &data[0], &data[missing_len]);
-      if (partial_buf_.size() < kHeaderBytes)
-        return util::OkStatus();
-      data += missing_len;
-      size -= missing_len;
-    }
-
-    // At this point we have enough data in |partial_buf_| to read at least the
-    // field header and know the size of the next TracePacket.
-    const uint8_t* pos = &partial_buf_[0];
-    uint8_t proto_field_tag = *pos;
-    uint64_t field_size = 0;
-    const uint8_t* next = ParseVarInt(++pos, &*partial_buf_.end(), &field_size);
-    bool parse_failed = next == pos;
-    pos = next;
-    if (proto_field_tag != kTracePacketTag || field_size == 0 || parse_failed) {
-      return util::ErrStatus(
-          "Failed parsing a TracePacket from the partial buffer");
-    }
-
-    // At this point we know how big the TracePacket is.
-    size_t hdr_size = static_cast<size_t>(pos - &partial_buf_[0]);
-    size_t size_incl_header = static_cast<size_t>(field_size + hdr_size);
-    PERFETTO_DCHECK(size_incl_header > partial_buf_.size());
-
-    // There is a good chance that between the |partial_buf_| and the new |data|
-    // of the current call we have enough bytes to parse a TracePacket.
-    if (partial_buf_.size() + size >= size_incl_header) {
-      // Create a new buffer for the whole TracePacket and copy into that:
-      // 1) The beginning of the TracePacket (including the proto header) from
-      //    the partial buffer.
-      // 2) The rest of the TracePacket from the current |data| buffer (note
-      //    that we might have consumed already a few bytes form |data| earlier
-      //    in this function, hence we need to keep |off| into account).
-      std::unique_ptr<uint8_t[]> buf(new uint8_t[size_incl_header]);
-      memcpy(&buf[0], partial_buf_.data(), partial_buf_.size());
-      // |size_missing| is the number of bytes for the rest of the TracePacket
-      // in |data|.
-      size_t size_missing = size_incl_header - partial_buf_.size();
-      memcpy(&buf[partial_buf_.size()], &data[0], size_missing);
-      data += size_missing;
-      size -= size_missing;
-      partial_buf_.clear();
-      uint8_t* buf_start = &buf[0];  // Note that buf is std::moved below.
-      util::Status status =
-          ParseInternal(std::move(buf), buf_start, size_incl_header);
-      if (PERFETTO_UNLIKELY(!status.ok()))
-        return status;
-    } else {
-      partial_buf_.insert(partial_buf_.end(), data, &data[size]);
-      return util::OkStatus();
-    }
-  }
-  return ParseInternal(std::move(owned_buf), data, size);
-}
-
-util::Status ProtoTraceTokenizer::ParseInternal(
-    std::unique_ptr<uint8_t[]> owned_buf,
-    uint8_t* data,
-    size_t size) {
-  PERFETTO_DCHECK(data >= &owned_buf[0]);
-  const uint8_t* start = &owned_buf[0];
-  const size_t data_off = static_cast<size_t>(data - start);
-  TraceBlobView whole_buf(std::move(owned_buf), data_off, size);
-
-  protos::pbzero::Trace::Decoder decoder(data, size);
-  for (auto it = decoder.packet(); it; ++it) {
-    protozero::ConstBytes packet = *it;
-    size_t field_offset = whole_buf.offset_of(packet.data);
-    util::Status status =
-        ParsePacket(whole_buf.slice(field_offset, packet.size));
-    if (PERFETTO_UNLIKELY(!status.ok()))
-      return status;
-  }
-
-  const size_t bytes_left = decoder.bytes_left();
-  if (bytes_left > 0) {
-    PERFETTO_DCHECK(partial_buf_.empty());
-    partial_buf_.insert(partial_buf_.end(), &data[decoder.read_offset()],
-                        &data[decoder.read_offset() + bytes_left]);
-  }
+  std::unique_ptr<uint8_t[]> out_data(new uint8_t[data.size()]);
+  memcpy(out_data.get(), data.data(), data.size());
+  *output = TraceBlobView(std::move(out_data), 0, data.size());
   return util::OkStatus();
 }
 
-util::Status ProtoTraceTokenizer::ParseExtensionDescriptor(
-    ConstBytes descriptor) {
-  protos::pbzero::ExtensionDescriptor::Decoder decoder(descriptor.data,
-                                                       descriptor.size);
-
-  auto extension = decoder.extension_set();
-  return context_->proto_to_args_table_->AddProtoFileDescriptor(extension.data,
-                                                                extension.size);
-}
-
-util::Status ProtoTraceTokenizer::ParsePacket(TraceBlobView packet) {
-  protos::pbzero::TracePacket::Decoder decoder(packet.data(), packet.length());
-  if (PERFETTO_UNLIKELY(decoder.bytes_left()))
-    return util::ErrStatus(
-        "Failed to parse proto packet fully; the trace is probably corrupt.");
-
-  const uint32_t seq_id = decoder.trusted_packet_sequence_id();
-  auto* state = GetIncrementalStateForPacketSequence(seq_id);
-
-  uint32_t sequence_flags = decoder.sequence_flags();
-
-  if (decoder.incremental_state_cleared() ||
-      sequence_flags &
-          protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
-    HandleIncrementalStateCleared(decoder);
-  } else if (decoder.previous_packet_dropped()) {
-    HandlePreviousPacketDropped(decoder);
-  }
-
-  // It is important that we parse defaults before parsing other fields such as
-  // the timestamp, since the defaults could affect them.
-  if (decoder.has_trace_packet_defaults()) {
-    auto field = decoder.trace_packet_defaults();
-    const size_t offset = packet.offset_of(field.data);
-    ParseTracePacketDefaults(decoder, packet.slice(offset, field.size));
-  }
-
-  if (decoder.has_interned_data()) {
-    auto field = decoder.interned_data();
-    const size_t offset = packet.offset_of(field.data);
-    ParseInternedData(decoder, packet.slice(offset, field.size));
-  }
-
-  if (decoder.has_clock_snapshot()) {
-    return ParseClockSnapshot(decoder.clock_snapshot(),
-                              decoder.trusted_packet_sequence_id());
-  }
-
-  if (decoder.has_service_event()) {
-    PERFETTO_DCHECK(decoder.has_timestamp());
-    int64_t ts = static_cast<int64_t>(decoder.timestamp());
-    return ParseServiceEvent(ts, decoder.service_event());
-  }
-
-  if (decoder.has_extension_descriptor()) {
-    return ParseExtensionDescriptor(decoder.extension_descriptor());
-  }
-
-  if (decoder.sequence_flags() &
-      protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE) {
-    if (!seq_id) {
-      return util::ErrStatus(
-          "TracePacket specified SEQ_NEEDS_INCREMENTAL_STATE but the "
-          "TraceWriter's sequence_id is zero (the service is "
-          "probably too old)");
-    }
-
-    if (!state->IsIncrementalStateValid()) {
-      context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-      return util::OkStatus();
-    }
-  }
-
-  protos::pbzero::TracePacketDefaults::Decoder* defaults =
-      state->current_generation()->GetTracePacketDefaults();
-
-  int64_t timestamp;
-  if (decoder.has_timestamp()) {
-    timestamp = static_cast<int64_t>(decoder.timestamp());
-
-    uint32_t timestamp_clock_id =
-        decoder.has_timestamp_clock_id()
-            ? decoder.timestamp_clock_id()
-            : (defaults ? defaults->timestamp_clock_id() : 0);
-
-    if ((decoder.has_chrome_events() || decoder.has_chrome_metadata()) &&
-        (!timestamp_clock_id ||
-         timestamp_clock_id == protos::pbzero::BUILTIN_CLOCK_MONOTONIC)) {
-      // Chrome event timestamps are in MONOTONIC domain, but may occur in
-      // traces where (a) no clock snapshots exist or (b) no clock_id is
-      // specified for their timestamps. Adjust to trace time if we have a clock
-      // snapshot.
-      // TODO(eseckler): Set timestamp_clock_id and emit ClockSnapshots in
-      // chrome and then remove this.
-      auto trace_ts = context_->clock_tracker->ToTraceTime(
-          protos::pbzero::BUILTIN_CLOCK_MONOTONIC, timestamp);
-      if (trace_ts.has_value())
-        timestamp = trace_ts.value();
-    } else if (timestamp_clock_id) {
-      // If the TracePacket specifies a non-zero clock-id, translate the
-      // timestamp into the trace-time clock domain.
-      ClockTracker::ClockId converted_clock_id = timestamp_clock_id;
-      bool is_seq_scoped =
-          ClockTracker::IsReservedSeqScopedClockId(converted_clock_id);
-      if (is_seq_scoped) {
-        if (!seq_id) {
-          return util::ErrStatus(
-              "TracePacket specified a sequence-local clock id (%" PRIu32
-              ") but the TraceWriter's sequence_id is zero (the service is "
-              "probably too old)",
-              timestamp_clock_id);
-        }
-        converted_clock_id =
-            ClockTracker::SeqScopedClockIdToGlobal(seq_id, timestamp_clock_id);
-      }
-      auto trace_ts =
-          context_->clock_tracker->ToTraceTime(converted_clock_id, timestamp);
-      if (!trace_ts.has_value()) {
-        // ToTraceTime() will increase the |clock_sync_failure| stat on failure.
-        static const char seq_extra_err[] =
-            " Because the clock id is sequence-scoped, the ClockSnapshot must "
-            "be emitted on the same TraceWriter sequence of the packet that "
-            "refers to that clock id.";
-        return util::ErrStatus(
-            "Failed to convert TracePacket's timestamp from clock_id=%" PRIu32
-            " seq_id=%" PRIu32
-            ". This is usually due to the lack of a prior ClockSnapshot "
-            "proto.%s",
-            timestamp_clock_id, seq_id, is_seq_scoped ? seq_extra_err : "");
-      }
-      timestamp = trace_ts.value();
-    }
-  } else {
-    timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());
-  }
-  latest_timestamp_ = std::max(timestamp, latest_timestamp_);
-
-  auto& modules = context_->modules_by_field;
-  for (uint32_t field_id = 1; field_id < modules.size(); ++field_id) {
-    if (modules[field_id] && decoder.Get(field_id).valid()) {
-      ModuleResult res = modules[field_id]->TokenizePacket(
-          decoder, &packet, timestamp, state, field_id);
-      if (!res.ignored())
-        return res.ToStatus();
-    }
-  }
-
-  if (decoder.has_compressed_packets()) {
-    if (!gzip::IsGzipSupported())
-      return util::Status("Cannot decode compressed packets. Zlib not enabled");
-
-    protozero::ConstBytes field = decoder.compressed_packets();
-    const size_t field_off = packet.offset_of(field.data);
-    TraceBlobView compressed_packets = packet.slice(field_off, field.size);
-    TraceBlobView packets =
-        Decompress(&decompressor_, std::move(compressed_packets));
-
-    const uint8_t* start = packets.data();
-    const uint8_t* end = packets.data() + packets.length();
-    const uint8_t* ptr = start;
-    while ((end - ptr) > 2) {
-      const uint8_t* packet_start = ptr;
-      if (PERFETTO_UNLIKELY(*ptr != kTracePacketTag))
-        return util::ErrStatus("Expected TracePacket tag");
-      uint64_t packet_size = 0;
-      ptr = ParseVarInt(++ptr, end, &packet_size);
-      size_t packet_offset = static_cast<size_t>(ptr - start);
-      ptr += packet_size;
-      if (PERFETTO_UNLIKELY((ptr - packet_start) < 2 || ptr > end))
-        return util::ErrStatus("Invalid packet size");
-      util::Status status = ParsePacket(
-          packets.slice(packet_offset, static_cast<size_t>(packet_size)));
-      if (PERFETTO_UNLIKELY(!status.ok()))
-        return status;
-    }
-    return util::OkStatus();
-  }
-
-  // If we're not forcing a full sort and this is a write_into_file trace, then
-  // use flush_period_ms as an indiciator for how big the sliding window for the
-  // sorter should be.
-  if (!context_->config.force_full_sort && decoder.has_trace_config()) {
-    auto config = decoder.trace_config();
-    protos::pbzero::TraceConfig::Decoder trace_config(config.data, config.size);
-
-    if (trace_config.write_into_file()) {
-      int64_t window_size_ns;
-      if (trace_config.has_flush_period_ms() &&
-          trace_config.flush_period_ms() > 0) {
-        // We use 2x the flush period as a margin of error to allow for any
-        // late flush responses to still be sorted correctly.
-        window_size_ns = static_cast<int64_t>(trace_config.flush_period_ms()) *
-                         2 * 1000 * 1000;
-      } else {
-        constexpr uint64_t kDefaultWindowNs =
-            180 * 1000 * 1000 * 1000ULL;  // 3 minutes.
-        PERFETTO_ELOG(
-            "It is strongly recommended to have flush_period_ms set when "
-            "write_into_file is turned on. You will likely have many dropped "
-            "events because of inability to sort the events correctly.");
-        window_size_ns = static_cast<int64_t>(kDefaultWindowNs);
-      }
-      context_->sorter->SetWindowSizeNs(window_size_ns);
-    }
-  }
-
-  // Use parent data and length because we want to parse this again
-  // later to get the exact type of the packet.
-  context_->sorter->PushTracePacket(timestamp, state, std::move(packet));
-
-  return util::OkStatus();
-}
-
-void ProtoTraceTokenizer::HandleIncrementalStateCleared(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG(
-        "incremental_state_cleared without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
-    return;
-  }
-  GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id())
-      ->OnIncrementalStateCleared();
-  context_->track_tracker->OnIncrementalStateCleared(
-      packet_decoder.trusted_packet_sequence_id());
-}
-
-void ProtoTraceTokenizer::HandlePreviousPacketDropped(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder) {
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG("previous_packet_dropped without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
-    return;
-  }
-  GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id())
-      ->OnPacketLoss();
-}
-
-void ProtoTraceTokenizer::ParseTracePacketDefaults(
-    const protos::pbzero::TracePacket_Decoder& packet_decoder,
-    TraceBlobView trace_packet_defaults) {
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG(
-        "TracePacketDefaults packet without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
-    return;
-  }
-
-  auto* state = GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id());
-  state->UpdateTracePacketDefaults(std::move(trace_packet_defaults));
-}
-
-void ProtoTraceTokenizer::ParseInternedData(
-    const protos::pbzero::TracePacket::Decoder& packet_decoder,
-    TraceBlobView interned_data) {
-  if (PERFETTO_UNLIKELY(!packet_decoder.has_trusted_packet_sequence_id())) {
-    PERFETTO_ELOG("InternedData packet without trusted_packet_sequence_id");
-    context_->storage->IncrementStats(stats::interned_data_tokenizer_errors);
-    return;
-  }
-
-  auto* state = GetIncrementalStateForPacketSequence(
-      packet_decoder.trusted_packet_sequence_id());
-
-  // Don't parse interned data entries until incremental state is valid, because
-  // they could otherwise be associated with the wrong generation in the state.
-  if (!state->IsIncrementalStateValid()) {
-    context_->storage->IncrementStats(stats::tokenizer_skipped_packets);
-    return;
-  }
-
-  // Store references to interned data submessages into the sequence's state.
-  protozero::ProtoDecoder decoder(interned_data.data(), interned_data.length());
-  for (protozero::Field f = decoder.ReadField(); f.valid();
-       f = decoder.ReadField()) {
-    auto bytes = f.as_bytes();
-    auto offset = interned_data.offset_of(bytes.data);
-    state->InternMessage(f.id(), interned_data.slice(offset, bytes.size));
-  }
-}
-
-util::Status ProtoTraceTokenizer::ParseClockSnapshot(ConstBytes blob,
-                                                     uint32_t seq_id) {
-  std::vector<ClockTracker::ClockValue> clocks;
-  protos::pbzero::ClockSnapshot::Decoder evt(blob.data, blob.size);
-  if (evt.primary_trace_clock()) {
-    context_->clock_tracker->SetTraceTimeClock(
-        static_cast<ClockTracker::ClockId>(evt.primary_trace_clock()));
-  }
-  for (auto it = evt.clocks(); it; ++it) {
-    protos::pbzero::ClockSnapshot::Clock::Decoder clk(*it);
-    ClockTracker::ClockId clock_id = clk.clock_id();
-    if (ClockTracker::IsReservedSeqScopedClockId(clk.clock_id())) {
-      if (!seq_id) {
-        return util::ErrStatus(
-            "ClockSnapshot packet is specifying a sequence-scoped clock id "
-            "(%" PRIu64 ") but the TracePacket sequence_id is zero",
-            clock_id);
-      }
-      clock_id = ClockTracker::SeqScopedClockIdToGlobal(seq_id, clk.clock_id());
-    }
-    int64_t unit_multiplier_ns =
-        clk.unit_multiplier_ns()
-            ? static_cast<int64_t>(clk.unit_multiplier_ns())
-            : 1;
-    clocks.emplace_back(clock_id, clk.timestamp(), unit_multiplier_ns,
-                        clk.is_incremental());
-  }
-  context_->clock_tracker->AddSnapshot(clocks);
-  return util::OkStatus();
-}
-
-util::Status ProtoTraceTokenizer::ParseServiceEvent(int64_t ts,
-                                                    ConstBytes blob) {
-  protos::pbzero::TracingServiceEvent::Decoder tse(blob);
-  if (tse.tracing_started()) {
-    context_->metadata_tracker->SetMetadata(metadata::tracing_started_ns,
-                                            Variadic::Integer(ts));
-  }
-  if (tse.tracing_disabled()) {
-    context_->metadata_tracker->SetMetadata(metadata::tracing_disabled_ns,
-                                            Variadic::Integer(ts));
-  }
-  if (tse.all_data_sources_started()) {
-    context_->metadata_tracker->SetMetadata(
-        metadata::all_data_source_started_ns, Variadic::Integer(ts));
-  }
-  return util::OkStatus();
-}
-
-void ProtoTraceTokenizer::NotifyEndOfFile() {}
-
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/proto_trace_tokenizer.h b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
index ba94d75..647d9de 100644
--- a/src/trace_processor/importers/proto/proto_trace_tokenizer.h
+++ b/src/trace_processor/importers/proto/proto_trace_tokenizer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -17,85 +17,173 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_
 
-#include <stdint.h>
-
-#include <memory>
 #include <vector>
 
-#include "src/trace_processor/chunked_trace_reader.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/gzip/gzip_utils.h"
-#include "src/trace_processor/importers/proto/proto_incremental_state.h"
 #include "src/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/util/status_macros.h"
 
-namespace protozero {
-struct ConstBytes;
-}
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
-
-namespace protos {
-namespace pbzero {
-class TracePacket_Decoder;
-}  // namespace pbzero
-}  // namespace protos
-
 namespace trace_processor {
 
-class PacketSequenceState;
-class TraceProcessorContext;
-class TraceSorter;
-class TraceStorage;
-
 // Reads a protobuf trace in chunks and extracts boundaries of trace packets
 // (or subfields, for the case of ftrace) with their timestamps.
-class ProtoTraceTokenizer : public ChunkedTraceReader {
+class ProtoTraceTokenizer {
  public:
-  // |reader| is the abstract method of getting chunks of size |chunk_size_b|
-  // from a trace file with these chunks parsed into |trace|.
-  explicit ProtoTraceTokenizer(TraceProcessorContext*);
-  ~ProtoTraceTokenizer() override;
+  ProtoTraceTokenizer();
 
-  // ChunkedTraceReader implementation.
-  util::Status Parse(std::unique_ptr<uint8_t[]>, size_t size) override;
-  void NotifyEndOfFile() override;
+  template <typename Callback = util::Status(TraceBlobView)>
+  util::Status Tokenize(std::unique_ptr<uint8_t[]> owned_buf,
+                        size_t size,
+                        Callback callback) {
+    uint8_t* data = &owned_buf[0];
+    if (!partial_buf_.empty()) {
+      // It takes ~5 bytes for a proto preamble + the varint size.
+      const size_t kHeaderBytes = 5;
+      if (PERFETTO_UNLIKELY(partial_buf_.size() < kHeaderBytes)) {
+        size_t missing_len = std::min(kHeaderBytes - partial_buf_.size(), size);
+        partial_buf_.insert(partial_buf_.end(), &data[0], &data[missing_len]);
+        if (partial_buf_.size() < kHeaderBytes)
+          return util::OkStatus();
+        data += missing_len;
+        size -= missing_len;
+      }
+
+      // At this point we have enough data in |partial_buf_| to read at least
+      // the field header and know the size of the next TracePacket.
+      const uint8_t* pos = &partial_buf_[0];
+      uint8_t proto_field_tag = *pos;
+      uint64_t field_size = 0;
+      // We cannot do &partial_buf_[partial_buf_.size()] because that crashes
+      // on MSVC STL debug builds, so does &*partial_buf_.end().
+      const uint8_t* next = protozero::proto_utils::ParseVarInt(
+          ++pos, &partial_buf_.front() + partial_buf_.size(), &field_size);
+      bool parse_failed = next == pos;
+      pos = next;
+      if (proto_field_tag != kTracePacketTag || field_size == 0 ||
+          parse_failed) {
+        return util::ErrStatus(
+            "Failed parsing a TracePacket from the partial buffer");
+      }
+
+      // At this point we know how big the TracePacket is.
+      size_t hdr_size = static_cast<size_t>(pos - &partial_buf_[0]);
+      size_t size_incl_header = static_cast<size_t>(field_size + hdr_size);
+      PERFETTO_DCHECK(size_incl_header > partial_buf_.size());
+
+      // There is a good chance that between the |partial_buf_| and the new
+      // |data| of the current call we have enough bytes to parse a TracePacket.
+      if (partial_buf_.size() + size >= size_incl_header) {
+        // Create a new buffer for the whole TracePacket and copy into that:
+        // 1) The beginning of the TracePacket (including the proto header) from
+        //    the partial buffer.
+        // 2) The rest of the TracePacket from the current |data| buffer (note
+        //    that we might have consumed already a few bytes form |data|
+        //    earlier in this function, hence we need to keep |off| into
+        //    account).
+        std::unique_ptr<uint8_t[]> buf(new uint8_t[size_incl_header]);
+        memcpy(&buf[0], partial_buf_.data(), partial_buf_.size());
+        // |size_missing| is the number of bytes for the rest of the TracePacket
+        // in |data|.
+        size_t size_missing = size_incl_header - partial_buf_.size();
+        memcpy(&buf[partial_buf_.size()], &data[0], size_missing);
+        data += size_missing;
+        size -= size_missing;
+        partial_buf_.clear();
+        uint8_t* buf_start = &buf[0];  // Note that buf is std::moved below.
+        RETURN_IF_ERROR(ParseInternal(std::move(buf), buf_start,
+                                      size_incl_header, callback));
+      } else {
+        partial_buf_.insert(partial_buf_.end(), data, &data[size]);
+        return util::OkStatus();
+      }
+    }
+    return ParseInternal(std::move(owned_buf), data, size, callback);
+  }
 
  private:
-  using ConstBytes = protozero::ConstBytes;
+  static constexpr uint8_t kTracePacketTag =
+      protozero::proto_utils::MakeTagLengthDelimited(
+          protos::pbzero::Trace::kPacketFieldNumber);
+
+  template <typename Callback = util::Status(TraceBlobView)>
   util::Status ParseInternal(std::unique_ptr<uint8_t[]> owned_buf,
                              uint8_t* data,
-                             size_t size);
-  util::Status ParsePacket(TraceBlobView);
-  util::Status ParseServiceEvent(int64_t ts, ConstBytes);
-  util::Status ParseClockSnapshot(ConstBytes blob, uint32_t seq_id);
-  void HandleIncrementalStateCleared(
-      const protos::pbzero::TracePacket_Decoder&);
-  void HandlePreviousPacketDropped(const protos::pbzero::TracePacket_Decoder&);
-  void ParseTracePacketDefaults(const protos::pbzero::TracePacket_Decoder&,
-                                TraceBlobView trace_packet_defaults);
-  void ParseInternedData(const protos::pbzero::TracePacket_Decoder&,
-                         TraceBlobView interned_data);
-  PacketSequenceState* GetIncrementalStateForPacketSequence(
-      uint32_t sequence_id) {
-    if (!incremental_state)
-      incremental_state.reset(new ProtoIncrementalState(context_));
-    return incremental_state->GetOrCreateStateForPacketSequence(sequence_id);
-  }
-  util::Status ParseExtensionDescriptor(ConstBytes descriptor);
+                             size_t size,
+                             Callback callback) {
+    PERFETTO_DCHECK(data >= &owned_buf[0]);
+    const uint8_t* start = &owned_buf[0];
+    const size_t data_off = static_cast<size_t>(data - start);
+    TraceBlobView whole_buf(std::move(owned_buf), data_off, size);
 
-  TraceProcessorContext* context_;
+    protos::pbzero::Trace::Decoder decoder(data, size);
+    for (auto it = decoder.packet(); it; ++it) {
+      protozero::ConstBytes packet = *it;
+      size_t field_offset = whole_buf.offset_of(packet.data);
+      TraceBlobView sliced = whole_buf.slice(field_offset, packet.size);
+      RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback));
+    }
+
+    const size_t bytes_left = decoder.bytes_left();
+    if (bytes_left > 0) {
+      PERFETTO_DCHECK(partial_buf_.empty());
+      partial_buf_.insert(partial_buf_.end(), &data[decoder.read_offset()],
+                          &data[decoder.read_offset() + bytes_left]);
+    }
+    return util::OkStatus();
+  }
+
+  template <typename Callback = util::Status(TraceBlobView)>
+  util::Status ParsePacket(TraceBlobView packet, Callback callback) {
+    protos::pbzero::TracePacket::Decoder decoder(packet.data(),
+                                                 packet.length());
+    if (decoder.has_compressed_packets()) {
+      if (!gzip::IsGzipSupported()) {
+        return util::Status(
+            "Cannot decode compressed packets. Zlib not enabled");
+      }
+
+      protozero::ConstBytes field = decoder.compressed_packets();
+      const size_t field_off = packet.offset_of(field.data);
+      TraceBlobView compressed_packets = packet.slice(field_off, field.size);
+      TraceBlobView packets(nullptr, 0, 0);
+
+      RETURN_IF_ERROR(Decompress(std::move(compressed_packets), &packets));
+
+      const uint8_t* start = packets.data();
+      const uint8_t* end = packets.data() + packets.length();
+      const uint8_t* ptr = start;
+      while ((end - ptr) > 2) {
+        const uint8_t* packet_start = ptr;
+        if (PERFETTO_UNLIKELY(*ptr != kTracePacketTag))
+          return util::ErrStatus("Expected TracePacket tag");
+        uint64_t packet_size = 0;
+        ptr = protozero::proto_utils::ParseVarInt(++ptr, end, &packet_size);
+        size_t packet_offset = static_cast<size_t>(ptr - start);
+        ptr += packet_size;
+        if (PERFETTO_UNLIKELY((ptr - packet_start) < 2 || ptr > end))
+          return util::ErrStatus("Invalid packet size");
+
+        TraceBlobView sliced =
+            packets.slice(packet_offset, static_cast<size_t>(packet_size));
+        RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback));
+      }
+      return util::OkStatus();
+    }
+    return callback(std::move(packet));
+  }
+
+  util::Status Decompress(TraceBlobView input, TraceBlobView* output);
 
   // Used to glue together trace packets that span across two (or more)
   // Parse() boundaries.
   std::vector<uint8_t> partial_buf_;
 
-  // Temporary. Currently trace packets do not have a timestamp, so the
-  // timestamp given is latest_timestamp_.
-  int64_t latest_timestamp_ = 0;
-
-  // Stores incremental state and references to interned data, e.g. for track
-  // event protos.
-  std::unique_ptr<ProtoIncrementalState> incremental_state;
-
   // Allows support for compressed trace packets.
   GzipDecompressor decompressor_;
 };
diff --git a/src/trace_processor/importers/proto/stack_profile_tracker.cc b/src/trace_processor/importers/proto/stack_profile_tracker.cc
index fb24528..7d8ee5f 100644
--- a/src/trace_processor/importers/proto/stack_profile_tracker.cc
+++ b/src/trace_processor/importers/proto/stack_profile_tracker.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 #include "perfetto/base/logging.h"
@@ -24,14 +25,15 @@
 namespace perfetto {
 namespace trace_processor {
 
-StackProfileTracker::InternLookup::~InternLookup() = default;
+SequenceStackProfileTracker::InternLookup::~InternLookup() = default;
 
-StackProfileTracker::StackProfileTracker(TraceProcessorContext* context)
+SequenceStackProfileTracker::SequenceStackProfileTracker(
+    TraceProcessorContext* context)
     : context_(context), empty_(kNullStringId) {}
 
-StackProfileTracker::~StackProfileTracker() = default;
+SequenceStackProfileTracker::~SequenceStackProfileTracker() = default;
 
-StringId StackProfileTracker::GetEmptyStringId() {
+StringId SequenceStackProfileTracker::GetEmptyStringId() {
   if (empty_ == kNullStringId) {
     empty_ = context_->storage->InternString({"", 0});
   }
@@ -39,11 +41,12 @@
   return empty_;
 }
 
-void StackProfileTracker::AddString(SourceStringId id, base::StringView str) {
+void SequenceStackProfileTracker::AddString(SourceStringId id,
+                                            base::StringView str) {
   string_map_.emplace(id, str.ToStdString());
 }
 
-base::Optional<MappingId> StackProfileTracker::AddMapping(
+base::Optional<MappingId> SequenceStackProfileTracker::AddMapping(
     SourceMappingId id,
     const SourceMapping& mapping,
     const InternLookup* intern_lookup) {
@@ -99,7 +102,8 @@
     cur_id = it->second;
   } else {
     std::vector<MappingId> db_mappings =
-        context_->storage->FindMappingRow(row.name, row.build_id);
+        context_->global_stack_profile_tracker->FindMappingRow(row.name,
+                                                               row.build_id);
     for (const MappingId preexisting_mapping : db_mappings) {
       uint32_t preexisting_row = *mappings->id().IndexOf(preexisting_mapping);
       tables::StackProfileMappingTable::Row preexisting_data{
@@ -117,7 +121,8 @@
     }
     if (!cur_id) {
       MappingId mapping_id = mappings->Insert(row).id;
-      context_->storage->InsertMappingId(row.name, row.build_id, mapping_id);
+      context_->global_stack_profile_tracker->InsertMappingId(
+          row.name, row.build_id, mapping_id);
       cur_id = mapping_id;
     }
     mapping_idx_.emplace(row, *cur_id);
@@ -126,18 +131,20 @@
   return cur_id;
 }
 
-base::Optional<FrameId> StackProfileTracker::AddFrame(
+base::Optional<FrameId> SequenceStackProfileTracker::AddFrame(
     SourceFrameId id,
     const SourceFrame& frame,
     const InternLookup* intern_lookup) {
-  auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup,
-                                        InternedStringType::kFunctionName);
-  if (!opt_str_id) {
+  base::Optional<std::string> opt_name = FindOrInsertString(
+      frame.name_id, intern_lookup, InternedStringType::kFunctionName);
+  if (!opt_name) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
     PERFETTO_DLOG("Invalid string.");
     return base::nullopt;
   }
-  const StringId& str_id = opt_str_id.value();
+  const std::string& name = *opt_name;
+  const StringId str_id =
+      context_->storage->InternString(base::StringView(name));
 
   auto opt_mapping = FindOrInsertMapping(frame.mapping_id, intern_lookup);
   if (!opt_mapping) {
@@ -145,6 +152,10 @@
     return base::nullopt;
   }
   MappingId mapping_id = *opt_mapping;
+  const auto& mappings = context_->storage->stack_profile_mapping_table();
+  StringId mapping_name_id =
+      mappings.name()[*mappings.id().IndexOf(mapping_id)];
+  auto mapping_name = context_->storage->GetString(mapping_name_id);
 
   tables::StackProfileFrameTable::Row row{str_id, mapping_id,
                                           static_cast<int64_t>(frame.rel_pc)};
@@ -157,7 +168,8 @@
     cur_id = it->second;
   } else {
     std::vector<FrameId> db_frames =
-        context_->storage->FindFrameIds(mapping_id, frame.rel_pc);
+        context_->global_stack_profile_tracker->FindFrameIds(mapping_id,
+                                                             frame.rel_pc);
     for (const FrameId preexisting_frame : db_frames) {
       uint32_t preexisting_row_id = *frames->id().IndexOf(preexisting_frame);
       tables::StackProfileFrameTable::Row preexisting_row{
@@ -171,8 +183,23 @@
     }
     if (!cur_id) {
       cur_id = frames->Insert(row).id;
-      context_->storage->InsertFrameRow(
+      context_->global_stack_profile_tracker->InsertFrameRow(
           mapping_id, static_cast<uint64_t>(row.rel_pc), *cur_id);
+      if (name.find('.') != std::string::npos) {
+        // Java frames always contain a '.'
+        base::Optional<std::string> package =
+            PackageFromLocation(context_->storage.get(), mapping_name);
+        if (package) {
+          NameInPackage nip{str_id, context_->storage->InternString(
+                                        base::StringView(*package))};
+          context_->global_stack_profile_tracker->InsertJavaFrameForName(
+              nip, *cur_id);
+        } else if (mapping_name.find("/memfd:") == 0) {
+          NameInPackage nip{str_id, context_->storage->InternString("memfd")};
+          context_->global_stack_profile_tracker->InsertJavaFrameForName(
+              nip, *cur_id);
+        }
+      }
     }
     frame_idx_.emplace(row, *cur_id);
   }
@@ -180,11 +207,11 @@
   return cur_id;
 }
 
-base::Optional<CallsiteId> StackProfileTracker::AddCallstack(
+base::Optional<CallsiteId> SequenceStackProfileTracker::AddCallstack(
     SourceCallstackId id,
     const SourceCallstack& frame_ids,
     const InternLookup* intern_lookup) {
-  if (frame_ids.size() == 0)
+  if (frame_ids.empty())
     return base::nullopt;
 
   base::Optional<CallsiteId> parent_id;
@@ -214,7 +241,7 @@
   return parent_id;
 }
 
-FrameId StackProfileTracker::GetDatabaseFrameIdForTesting(
+FrameId SequenceStackProfileTracker::GetDatabaseFrameIdForTesting(
     SourceFrameId frame_id) {
   auto it = frame_ids_.find(frame_id);
   if (it == frame_ids_.end()) {
@@ -224,10 +251,10 @@
   return it->second;
 }
 
-base::Optional<StringId> StackProfileTracker::FindAndInternString(
+base::Optional<StringId> SequenceStackProfileTracker::FindAndInternString(
     SourceStringId id,
     const InternLookup* intern_lookup,
-    StackProfileTracker::InternedStringType type) {
+    SequenceStackProfileTracker::InternedStringType type) {
   if (id == 0)
     return GetEmptyStringId();
 
@@ -238,10 +265,10 @@
   return context_->storage->InternString(base::StringView(*opt_str));
 }
 
-base::Optional<std::string> StackProfileTracker::FindOrInsertString(
+base::Optional<std::string> SequenceStackProfileTracker::FindOrInsertString(
     SourceStringId id,
     const InternLookup* intern_lookup,
-    StackProfileTracker::InternedStringType type) {
+    SequenceStackProfileTracker::InternedStringType type) {
   if (id == 0)
     return "";
 
@@ -263,7 +290,7 @@
   return it->second;
 }
 
-base::Optional<MappingId> StackProfileTracker::FindOrInsertMapping(
+base::Optional<MappingId> SequenceStackProfileTracker::FindOrInsertMapping(
     SourceMappingId mapping_id,
     const InternLookup* intern_lookup) {
   base::Optional<MappingId> res;
@@ -283,7 +310,7 @@
   return res;
 }
 
-base::Optional<FrameId> StackProfileTracker::FindOrInsertFrame(
+base::Optional<FrameId> SequenceStackProfileTracker::FindOrInsertFrame(
     SourceFrameId frame_id,
     const InternLookup* intern_lookup) {
   base::Optional<FrameId> res;
@@ -305,7 +332,7 @@
   return res;
 }
 
-base::Optional<CallsiteId> StackProfileTracker::FindOrInsertCallstack(
+base::Optional<CallsiteId> SequenceStackProfileTracker::FindOrInsertCallstack(
     SourceCallstackId callstack_id,
     const InternLookup* intern_lookup) {
   base::Optional<CallsiteId> res;
@@ -325,7 +352,7 @@
   return res;
 }
 
-void StackProfileTracker::ClearIndices() {
+void SequenceStackProfileTracker::ClearIndices() {
   string_map_.clear();
   mapping_ids_.clear();
   callstack_ids_.clear();
diff --git a/src/trace_processor/importers/proto/stack_profile_tracker.h b/src/trace_processor/importers/proto/stack_profile_tracker.h
index 27ae226..ddcd4df 100644
--- a/src/trace_processor/importers/proto/stack_profile_tracker.h
+++ b/src/trace_processor/importers/proto/stack_profile_tracker.h
@@ -25,6 +25,7 @@
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables.h"
 
 namespace std {
 
@@ -90,11 +91,73 @@
 namespace perfetto {
 namespace trace_processor {
 
+struct NameInPackage {
+  StringId name;
+  StringId package;
+
+  bool operator<(const NameInPackage& b) const {
+    return std::tie(name, package) < std::tie(b.name, b.package);
+  }
+};
+
 class TraceProcessorContext;
 
+class GlobalStackProfileTracker {
+ public:
+  std::vector<MappingId> FindMappingRow(StringId name,
+                                        StringId build_id) const {
+    auto it = stack_profile_mapping_index_.find(std::make_pair(name, build_id));
+    if (it == stack_profile_mapping_index_.end())
+      return {};
+    return it->second;
+  }
+
+  void InsertMappingId(StringId name, StringId build_id, MappingId row) {
+    auto pair = std::make_pair(name, build_id);
+    stack_profile_mapping_index_[pair].emplace_back(row);
+  }
+
+  std::vector<FrameId> FindFrameIds(MappingId mapping_row,
+                                    uint64_t rel_pc) const {
+    auto it =
+        stack_profile_frame_index_.find(std::make_pair(mapping_row, rel_pc));
+    if (it == stack_profile_frame_index_.end())
+      return {};
+    return it->second;
+  }
+
+  void InsertFrameRow(MappingId mapping_row, uint64_t rel_pc, FrameId row) {
+    auto pair = std::make_pair(mapping_row, rel_pc);
+    stack_profile_frame_index_[pair].emplace_back(row);
+  }
+
+  const std::vector<tables::StackProfileFrameTable::Id>* JavaFramesForName(
+      NameInPackage name) {
+    auto it = java_frames_for_name_.find(name);
+    if (it == java_frames_for_name_.end())
+      return nullptr;
+    return &it->second;
+  }
+
+  void InsertJavaFrameForName(NameInPackage name,
+                              tables::StackProfileFrameTable::Id id) {
+    java_frames_for_name_[name].push_back(id);
+  }
+
+ private:
+  using MappingKey = std::pair<StringId /* name */, StringId /* build id */>;
+  std::map<MappingKey, std::vector<MappingId>> stack_profile_mapping_index_;
+
+  using FrameKey = std::pair<MappingId, uint64_t /* rel_pc */>;
+  std::map<FrameKey, std::vector<FrameId>> stack_profile_frame_index_;
+
+  std::map<NameInPackage, std::vector<tables::StackProfileFrameTable::Id>>
+      java_frames_for_name_;
+};
+
 // TODO(lalitm): Overhaul this class to make row vs id consistent and use
 // base::Optional instead of int64_t.
-class StackProfileTracker {
+class SequenceStackProfileTracker {
  public:
   using SourceStringId = uint64_t;
 
@@ -150,8 +213,8 @@
         SourceCallstackId) const = 0;
   };
 
-  explicit StackProfileTracker(TraceProcessorContext* context);
-  ~StackProfileTracker();
+  explicit SequenceStackProfileTracker(TraceProcessorContext* context);
+  ~SequenceStackProfileTracker();
 
   void AddString(SourceStringId, base::StringView);
   base::Optional<MappingId> AddMapping(
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 15cd87a..d81e335 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -69,6 +69,12 @@
       cpu_times_softirq_ns_id_(
           context->storage->InternString("cpu.times.softirq_ns")),
       oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
+      is_peak_rss_resettable_id_(
+          context->storage->InternString("is_peak_rss_resettable")),
+      chrome_private_footprint_kb_(
+          context->storage->InternString("chrome.private_footprint_kb")),
+      chrome_peak_resident_set_kb_(
+          context->storage->InternString("chrome.peak_resident_set_kb")),
       thread_time_in_state_id_(context->storage->InternString("time_in_state")),
       thread_time_in_state_cpu_id_(
           context_->storage->InternString("time_in_state_cpu_id")),
@@ -99,6 +105,12 @@
       context->storage->InternString("mem.rss.watermark");
   proc_stats_process_names_[ProcessStats::Process::kOomScoreAdjFieldNumber] =
       oom_score_adj_id_;
+  proc_stats_process_names_
+      [ProcessStats::Process::kChromePrivateFootprintKbFieldNumber] =
+          chrome_private_footprint_kb_;
+  proc_stats_process_names_
+      [ProcessStats::Process::kChromePeakResidentSetKbFieldNumber] =
+          chrome_peak_resident_set_kb_;
 }
 
 void SystemProbesParser::ParseSysStats(int64_t ts, ConstBytes blob) {
@@ -281,11 +293,19 @@
 
     protozero::ProtoDecoder proc(*it);
     uint32_t pid = 0;
+    bool is_peak_rss_resettable = false;
+    bool has_peak_rrs_resettable = false;
     for (auto fld = proc.ReadField(); fld.valid(); fld = proc.ReadField()) {
       if (fld.id() == protos::pbzero::ProcessStats::Process::kPidFieldNumber) {
         pid = fld.as_uint32();
         continue;
       }
+      if (fld.id() == protos::pbzero::ProcessStats::Process::
+                          kIsPeakRssResettableFieldNumber) {
+        is_peak_rss_resettable = fld.as_bool();
+        has_peak_rrs_resettable = true;
+        continue;
+      }
       if (fld.id() ==
           protos::pbzero::ProcessStats::Process::kThreadsFieldNumber) {
         if (PERFETTO_UNLIKELY(ms_per_tick_ == 0 ||
@@ -311,18 +331,27 @@
       }
     }
 
+    if (has_peak_rrs_resettable) {
+      UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
+      context_->process_tracker->AddArgsTo(upid).AddArg(
+          is_peak_rss_resettable_id_,
+          Variadic::Boolean(is_peak_rss_resettable));
+    }
     // Skip field_id 0 (invalid) and 1 (pid).
     for (size_t field_id = 2; field_id < counter_values.size(); field_id++) {
-      if (!has_counter[field_id])
+      if (!has_counter[field_id] || field_id ==
+                                        protos::pbzero::ProcessStats::Process::
+                                            kIsPeakRssResettableFieldNumber) {
         continue;
+      }
 
       // Lookup the interned string id from the field name using the
       // pre-cached |proc_stats_process_names_| map.
       StringId name = proc_stats_process_names_[field_id];
-      int64_t value = counter_values[field_id];
       UniquePid upid = context_->process_tracker->GetOrCreateProcess(pid);
       TrackId track =
           context_->track_tracker->InternProcessCounterTrack(name, upid);
+      int64_t value = counter_values[field_id];
       context_->event_tracker->PushCounter(ts, static_cast<double>(value),
                                            track);
     }
@@ -379,14 +408,19 @@
                                              utsname_blob.size);
     base::StringView machine = utsname.machine();
     SyscallTracker* syscall_tracker = SyscallTracker::GetOrCreate(context_);
-    if (machine == "aarch64" || machine == "armv8l") {
+    if (machine == "aarch64") {
       syscall_tracker->SetArchitecture(kAarch64);
+    } else if (machine == "armv8l") {
+      syscall_tracker->SetArchitecture(kArmEabi);
+    } else if (machine == "armv7l") {
+      syscall_tracker->SetArchitecture(kAarch32);
     } else if (machine == "x86_64") {
       syscall_tracker->SetArchitecture(kX86_64);
     } else if (machine == "i686") {
       syscall_tracker->SetArchitecture(kX86);
     } else {
-      PERFETTO_ELOG("Unknown architecture %s", machine.ToStdString().c_str());
+      PERFETTO_ELOG("Unknown architecture %s. Syscall traces will not work.",
+                    machine.ToStdString().c_str());
     }
 
     SystemInfoTracker* system_info_tracker =
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 5485b36..b604b6c 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -60,6 +60,9 @@
   const StringId cpu_times_irq_ns_id_;
   const StringId cpu_times_softirq_ns_id_;
   const StringId oom_score_adj_id_;
+  const StringId is_peak_rss_resettable_id_;
+  const StringId chrome_private_footprint_kb_;
+  const StringId chrome_peak_resident_set_kb_;
   const StringId thread_time_in_state_id_;
   const StringId thread_time_in_state_cpu_id_;
   const StringId cpu_freq_id_;
@@ -69,7 +72,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.
-  static constexpr size_t kProcStatsProcessSize = 11;
+  static constexpr size_t kProcStatsProcessSize = 15;
   std::array<StringId, kProcStatsProcessSize> proc_stats_process_names_{};
 
   uint64_t ms_per_tick_ = 0;
diff --git a/src/trace_processor/importers/proto/track_event.descriptor.h b/src/trace_processor/importers/proto/track_event.descriptor.h
index 6a5d5ca..6187599 100644
--- a/src/trace_processor/importers/proto/track_event.descriptor.h
+++ b/src/trace_processor/importers/proto/track_event.descriptor.h
@@ -25,16 +25,16 @@
 // This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
 
 // SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
+// e5c244903aa00cad06faf3d126918306a7fe811e
 // SHA1(protos/perfetto/trace/track_event/track_event.proto)
-// 723097690a042f5be26a438547bd5cca56ed3e5a
+// 973e7d6ad81b9b7a4e1e3c0d2aa7f629f7432a15
 
 // This is the proto TrackEvent encoded as a ProtoFileDescriptor to allow
 // for reflection without libprotobuf full/non-lite protos.
 
 namespace perfetto {
 
-constexpr std::array<uint8_t, 18918> kTrackEventDescriptor{
+constexpr std::array<uint8_t, 21487> kTrackEventDescriptor{
     {0x0a, 0x96, 0x08, 0x0a, 0x38, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
      0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61,
      0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65,
@@ -151,1153 +151,1257 @@
      0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x6f, 0x73, 0x74,
      0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x69, 0x69, 0x64, 0x18,
      0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x74, 0x65,
-     0x64, 0x46, 0x72, 0x6f, 0x6d, 0x49, 0x69, 0x64, 0x0a, 0xd2, 0x01, 0x0a,
-     0x37, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
+     0x64, 0x46, 0x72, 0x6f, 0x6d, 0x49, 0x69, 0x64, 0x0a, 0xdb, 0x03, 0x0a,
+     0x45, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
      0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74,
-     0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x73,
-     0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-     0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x22, 0x85, 0x01, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
-     0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03,
-     0x69, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x69,
-     0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66,
-     0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x66,
-     0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e, 0x63,
-     0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
-     0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x4e,
-     0x75, 0x6d, 0x62, 0x65, 0x72, 0x0a, 0xb5, 0x4d, 0x0a, 0x49, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
-     0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f,
-     0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x37, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76,
-     0x65, 0x6e, 0x74, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c,
-     0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x22, 0xe6, 0x0b, 0x0a, 0x1e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
-     0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63,
-     0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x12, 0x52, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61,
-     0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
-     0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x0c,
-     0x73, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
-     0x12, 0x3f, 0x0a, 0x1c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
-     0x67, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d,
-     0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x08, 0x52, 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
-     0x67, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
-     0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x1e, 0x62, 0x65, 0x67,
-     0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d,
-     0x65, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x74,
-     0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x61, 0x73,
-     0x6b, 0x12, 0x37, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-     0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
-     0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x65, 0x67, 0x69,
-     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x5b,
-     0x0a, 0x2b, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x61,
-     0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x73,
-     0x73, 0x65, 0x64, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64,
-     0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x26, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64,
-     0x4c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x69, 0x73,
-     0x73, 0x65, 0x64, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x44,
-     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x73,
-     0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x64,
-     0x75, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18,
-     0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x73, 0x6b, 0x69, 0x70, 0x70,
-     0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54,
-     0x6f, 0x52, 0x65, 0x64, 0x75, 0x63, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e,
-     0x63, 0x79, 0x12, 0x55, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65,
-     0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,
-     0x0e, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
+     0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x22, 0x80, 0x03, 0x0a, 0x1a, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+     0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x6f,
+     0x0a, 0x11, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+     0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x0e, 0x32, 0x42, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
      0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
-     0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x41, 0x63, 0x74,
-     0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x41,
-     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6f, 0x0a, 0x0d, 0x64, 0x65, 0x61,
-     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x08,
-     0x20, 0x01, 0x28, 0x0e, 0x32, 0x4a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43,
-     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69,
-     0x74, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49,
-     0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x61, 0x64,
-     0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0c, 0x64, 0x65,
-     0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1f,
-     0x0a, 0x0b, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75,
-     0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x61,
-     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x64,
-     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65,
-     0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18,
-     0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c,
-     0x69, 0x6e, 0x65, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64,
-     0x41, 0x74, 0x55, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f,
-     0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f,
-     0x77, 0x55, 0x73, 0x12, 0x36, 0x0a, 0x18, 0x6e, 0x6f, 0x77, 0x5f, 0x74,
-     0x6f, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64,
-     0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x14, 0x6e, 0x6f, 0x77, 0x54, 0x6f, 0x44, 0x65, 0x61, 0x64,
-     0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12,
-     0x4e, 0x0a, 0x25, 0x6e, 0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65,
-     0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64,
-     0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74,
-     0x61, 0x5f, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1f,
-     0x6e, 0x6f, 0x77, 0x54, 0x6f, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e,
-     0x65, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74,
-     0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x56, 0x0a, 0x15, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x0e, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67,
-     0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41,
-     0x72, 0x67, 0x73, 0x52, 0x12, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d,
-     0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x12,
-     0x65, 0x0a, 0x1a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61,
-     0x6d, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
-     0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x28, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46,
-     0x72, 0x61, 0x6d, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x17, 0x62, 0x65, 0x67, 0x69, 0x6e,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65,
-     0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x5f, 0x0a, 0x18, 0x62, 0x65,
-     0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x6f,
-     0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x10,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x15, 0x62, 0x65,
-     0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x64, 0x0a, 0x19, 0x63,
-     0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x69,
-     0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,
-     0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x54,
-     0x69, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,
-     0x52, 0x17, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
-     0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72,
-     0x79, 0x22, 0xbe, 0x01, 0x0a, 0x1a, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49,
-     0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x61, 0x64,
-     0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x19,
-     0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44,
-     0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
-     0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x41, 0x44, 0x4c,
-     0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x4e,
-     0x45, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x41, 0x44, 0x4c,
-     0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4d, 0x4d,
-     0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15,
-     0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44,
-     0x45, 0x5f, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52, 0x10, 0x03, 0x12,
-     0x16, 0x0a, 0x12, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f,
-     0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x10, 0x04, 0x12,
-     0x19, 0x0a, 0x15, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f,
-     0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44,
-     0x10, 0x05, 0x22, 0x86, 0x28, 0x0a, 0x1c, 0x43, 0x68, 0x72, 0x6f, 0x6d,
-     0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12,
-     0x59, 0x0a, 0x0b, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61,
-     0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70,
+     0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+     0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x43,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
+     0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x10, 0x61,
+     0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
+     0x61, 0x74, 0x65, 0x22, 0xf0, 0x01, 0x0a, 0x16, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+     0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x50,
+     0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54,
+     0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
+     0x00, 0x12, 0x2c, 0x0a, 0x28, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41,
+     0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x48,
+     0x41, 0x53, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x56, 0x49, 0x54, 0x49, 0x45, 0x53, 0x10, 0x01, 0x12,
+     0x2b, 0x0a, 0x27, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49,
+     0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x48, 0x41, 0x53,
+     0x5f, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x41, 0x43, 0x54, 0x49,
+     0x56, 0x49, 0x54, 0x49, 0x45, 0x53, 0x10, 0x02, 0x12, 0x2c, 0x0a, 0x28,
+     0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
+     0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x48, 0x41, 0x53, 0x5f, 0x53, 0x54,
+     0x4f, 0x50, 0x50, 0x45, 0x44, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x49,
+     0x54, 0x49, 0x45, 0x53, 0x10, 0x03, 0x12, 0x2e, 0x0a, 0x2a, 0x41, 0x50,
+     0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54,
+     0x41, 0x54, 0x45, 0x5f, 0x48, 0x41, 0x53, 0x5f, 0x44, 0x45, 0x53, 0x54,
+     0x52, 0x4f, 0x59, 0x45, 0x44, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x49,
+     0x54, 0x49, 0x45, 0x53, 0x10, 0x04, 0x0a, 0xd2, 0x01, 0x0a, 0x37, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61,
+     0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x73, 0x6f, 0x75,
+     0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
+     0x85, 0x01, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f,
+     0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x69,
+     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x69, 0x69, 0x64,
+     0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c,
+     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x75, 0x6e,
+     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+     0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
+     0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69,
+     0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20,
+     0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d,
+     0x62, 0x65, 0x72, 0x0a, 0xb5, 0x4d, 0x0a, 0x49, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f,
+     0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x5f,
+     0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74,
+     0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
      0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d,
-     0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61, 0x6a, 0x6f,
-     0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6d, 0x61, 0x6a, 0x6f,
-     0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x59, 0x0a, 0x0b, 0x6d, 0x69,
-     0x6e, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74,
-     0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
-     0x6e, 0x65, 0x2e, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x1a, 0xf9, 0x0a, 0x0a, 0x0a, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74,
-     0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0e, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x6f, 0x73, 0x1a, 0x37, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
+     0xe6, 0x0b, 0x0a, 0x1e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f,
+     0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65,
+     0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x52,
+     0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x63, 0x68,
+     0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f,
+     0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x0c, 0x73, 0x74,
+     0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x3f,
+     0x0a, 0x1c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x5f,
+     0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
+     0x52, 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x42,
+     0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75,
+     0x72, 0x63, 0x65, 0x12, 0x42, 0x0a, 0x1e, 0x62, 0x65, 0x67, 0x69, 0x6e,
+     0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
+     0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x61, 0x73,
+     0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x62, 0x65, 0x67,
+     0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44,
+     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12,
+     0x37, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62,
+     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74,
+     0x61, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x70,
+     0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x5b, 0x0a, 0x2b,
+     0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x65,
+     0x64, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x5f, 0x64,
+     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x26, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x4c, 0x61,
+     0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x65,
+     0x64, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x44, 0x65, 0x61,
+     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x73, 0x6b, 0x69,
+     0x70, 0x70, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72,
+     0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x64, 0x75, 0x63,
+     0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x1f, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64,
+     0x4c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x52,
+     0x65, 0x64, 0x75, 0x63, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79,
+     0x12, 0x55, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x61,
+     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32,
+     0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63,
+     0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f,
+     0x6e, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x41, 0x63, 0x74,
+     0x69, 0x6f, 0x6e, 0x12, 0x6f, 0x0a, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c,
+     0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01,
+     0x28, 0x0e, 0x32, 0x4a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+     0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74,
+     0x61, 0x74, 0x65, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70,
+     0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69,
+     0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0c, 0x64, 0x65, 0x61, 0x64,
+     0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
+     0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75, 0x73, 0x18,
+     0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x61, 0x64, 0x6c,
+     0x69, 0x6e, 0x65, 0x55, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x64, 0x65, 0x61,
+     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75,
+     0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x0a, 0x20,
+     0x01, 0x28, 0x03, 0x52, 0x15, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e,
+     0x65, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74,
+     0x55, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6e, 0x6f, 0x77, 0x5f, 0x75, 0x73,
+     0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6e, 0x6f, 0x77, 0x55,
+     0x73, 0x12, 0x36, 0x0a, 0x18, 0x6e, 0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x5f,
+     0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c,
+     0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52,
+     0x14, 0x6e, 0x6f, 0x77, 0x54, 0x6f, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69,
+     0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x4e, 0x0a,
+     0x25, 0x6e, 0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x61, 0x64,
+     0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
+     0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f,
+     0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1f, 0x6e, 0x6f,
+     0x77, 0x54, 0x6f, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x53,
+     0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x44, 0x65,
+     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x56, 0x0a, 0x15, 0x62, 0x65, 0x67,
+     0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e,
+     0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67,
+     0x73, 0x52, 0x12, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x12, 0x65, 0x0a,
+     0x1a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
+     0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74,
+     0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74,
+     0x61, 0x74, 0x65, 0x52, 0x17, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72,
+     0x61, 0x6d, 0x65, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x12, 0x5f, 0x0a, 0x18, 0x62, 0x65, 0x67, 0x69,
+     0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72,
+     0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01,
+     0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63,
+     0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x15, 0x62, 0x65, 0x67, 0x69,
+     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x64, 0x0a, 0x19, 0x63, 0x6f, 0x6d,
+     0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x69,
+     0x6e, 0x67, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x11,
+     0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
+     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43,
+     0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x54, 0x69, 0x6d,
+     0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x17,
+     0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x54, 0x69,
+     0x6d, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22,
+     0xbe, 0x01, 0x0a, 0x1a, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70,
+     0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69,
+     0x6e, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x44, 0x45,
+     0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f,
+     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+     0x00, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e,
+     0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10,
+     0x01, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e,
+     0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x4d, 0x4d, 0x45, 0x44,
+     0x49, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x45,
+     0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f,
+     0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52, 0x10, 0x03, 0x12, 0x16, 0x0a,
+     0x12, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f,
+     0x44, 0x45, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x10, 0x04, 0x12, 0x19, 0x0a,
+     0x15, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4d, 0x4f,
+     0x44, 0x45, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x05,
+     0x22, 0x86, 0x28, 0x0a, 0x1c, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43,
+     0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x59, 0x0a,
+     0x0b, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f,
+     0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61,
+     0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x12, 0x59, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x6f,
+     0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x38, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
      0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f,
      0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
-     0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x41, 0x63, 0x74,
-     0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x41, 0x63, 0x74,
-     0x69, 0x6f, 0x6e, 0x12, 0x81, 0x01, 0x0a, 0x16, 0x62, 0x65, 0x67, 0x69,
-     0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
-     0x32, 0x4c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
-     0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e,
-     0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x13, 0x62, 0x65, 0x67, 0x69,
-     0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x16, 0x62, 0x65, 0x67, 0x69,
-     0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
-     0x32, 0x4c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
-     0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e,
-     0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x13, 0x62, 0x65, 0x67, 0x69,
-     0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x12, 0x8e, 0x01, 0x0a, 0x1b, 0x6c, 0x61, 0x79, 0x65,
-     0x72, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x73, 0x69, 0x6e, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x50, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73,
-     0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63,
-     0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65,
-     0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x52, 0x17, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72,
-     0x65, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x13, 0x66, 0x6f, 0x72,
-     0x63, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x73,
-     0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x53,
+     0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
+     0x2e, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
+     0x0a, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a,
+     0xf9, 0x0a, 0x0a, 0x0a, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x61,
+     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+     0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63,
+     0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f,
+     0x6e, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f,
+     0x6e, 0x12, 0x81, 0x01, 0x0a, 0x16, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f,
+     0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73,
+     0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x4c,
      0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
      0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43,
      0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61,
      0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61,
-     0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x46, 0x6f, 0x72,
-     0x63, 0x65, 0x64, 0x52, 0x65, 0x64, 0x72, 0x61, 0x77, 0x4f, 0x6e, 0x54,
-     0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
-     0x11, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x64, 0x52, 0x65, 0x64, 0x72, 0x61,
-     0x77, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0xa1, 0x01, 0x0a, 0x13, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45,
-     0x47, 0x49, 0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41,
-     0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
-     0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x45, 0x47, 0x49,
-     0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
-     0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x27, 0x0a, 0x23, 0x42,
-     0x45, 0x47, 0x49, 0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52,
-     0x41, 0x4d, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x42,
-     0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x02,
-     0x12, 0x24, 0x0a, 0x20, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x49, 0x4d,
-     0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x49, 0x4e, 0x53,
-     0x49, 0x44, 0x45, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45,
-     0x10, 0x03, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x42, 0x65, 0x67, 0x69, 0x6e,
-     0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61,
-     0x74, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f,
-     0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55,
-     0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
-     0x12, 0x19, 0x0a, 0x15, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41,
-     0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x49, 0x44, 0x4c,
-     0x45, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x45, 0x47, 0x49, 0x4e,
-     0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f,
-     0x53, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x42, 0x45,
-     0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41,
-     0x4d, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x54, 0x4f, 0x5f,
-     0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x03, 0x22, 0xf4, 0x01, 0x0a,
-     0x17, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x12, 0x20, 0x0a, 0x1c, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52,
-     0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x53,
-     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19,
-     0x0a, 0x15, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45,
-     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10,
-     0x01, 0x12, 0x1b, 0x0a, 0x17, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54,
-     0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x43,
-     0x54, 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x4c, 0x41,
-     0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41,
-     0x4d, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10,
-     0x03, 0x12, 0x2d, 0x0a, 0x29, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54,
-     0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x57, 0x41,
-     0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x46, 0x49,
-     0x52, 0x53, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04,
-     0x12, 0x31, 0x0a, 0x2d, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52,
-     0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x57, 0x41, 0x49,
-     0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x52,
-     0x53, 0x54, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4f,
-     0x4e, 0x10, 0x05, 0x22, 0xc7, 0x01, 0x0a, 0x1a, 0x46, 0x6f, 0x72, 0x63,
-     0x65, 0x64, 0x52, 0x65, 0x64, 0x72, 0x61, 0x77, 0x4f, 0x6e, 0x54, 0x69,
-     0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d,
-     0x0a, 0x19, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44,
-     0x52, 0x41, 0x57, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-     0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x46, 0x4f, 0x52,
-     0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x49,
-     0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x4f, 0x52,
-     0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x57,
-     0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x43,
-     0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x28, 0x0a, 0x24, 0x46,
-     0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57,
-     0x5f, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52,
-     0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10,
-     0x03, 0x12, 0x22, 0x0a, 0x1e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f,
-     0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x49,
-     0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x10,
-     0x04, 0x1a, 0xb3, 0x1b, 0x0a, 0x0a, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d,
-     0x69, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65,
-     0x6e, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d,
-     0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63,
-     0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e,
-     0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x22, 0x6c, 0x61, 0x73,
-     0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62,
-     0x65, 0x72, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x70, 0x65,
-     0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x05, 0x52, 0x1e, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
-     0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x46, 0x0a,
-     0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
-     0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f,
-     0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x04, 0x20,
-     0x01, 0x28, 0x05, 0x52, 0x1c, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x44, 0x72, 0x61, 0x77,
-     0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x52, 0x0a,
-     0x27, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
-     0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e,
-     0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
-     0x73, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x21,
-     0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d,
-     0x62, 0x65, 0x72, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a,
-     0x08, 0x64, 0x69, 0x64, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x69, 0x64, 0x44, 0x72, 0x61, 0x77,
-     0x12, 0x59, 0x0a, 0x2b, 0x64, 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6e, 0x64,
-     0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x63, 0x75,
-     0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18,
-     0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x64, 0x69, 0x64, 0x53, 0x65,
-     0x6e, 0x64, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46,
-     0x72, 0x61, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65,
-     0x6e, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x5f, 0x0a, 0x2e, 0x64,
-     0x69, 0x64, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x62, 0x65,
-     0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61,
-     0x6d, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x65, 0x63,
-     0x74, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x08, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x27, 0x64, 0x69, 0x64, 0x4e, 0x6f, 0x74, 0x69,
-     0x66, 0x79, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46,
-     0x72, 0x61, 0x6d, 0x65, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63,
-     0x74, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x12, 0x5d, 0x0a, 0x2d,
-     0x64, 0x69, 0x64, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x65,
-     0x63, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x6f, 0x6e, 0x18, 0x09, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x26, 0x64, 0x69, 0x64, 0x4e, 0x6f, 0x74, 0x69,
-     0x66, 0x79, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46,
-     0x72, 0x61, 0x6d, 0x65, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63,
-     0x74, 0x65, 0x64, 0x53, 0x6f, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x23, 0x77,
-     0x61, 0x6e, 0x74, 0x73, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d,
-     0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x6f,
-     0x74, 0x5f, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x0a,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
-     0x12, 0x35, 0x0a, 0x17, 0x64, 0x69, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
-     0x69, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64,
-     0x69, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x69,
-     0x6e, 0x67, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x64,
-     0x69, 0x64, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
-     0x65, 0x5f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x6e, 0x6b, 0x18,
-     0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x64, 0x69, 0x64, 0x49, 0x6e,
-     0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x79, 0x65,
-     0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69,
-     0x6e, 0x6b, 0x12, 0x48, 0x0a, 0x21, 0x64, 0x69, 0x64, 0x5f, 0x70, 0x65,
-     0x72, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x73,
-     0x69, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
-     0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x64,
-     0x69, 0x64, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6d, 0x70,
-     0x6c, 0x53, 0x69, 0x64, 0x65, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64,
-     0x61, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x64, 0x69, 0x64, 0x5f,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6c, 0x65,
-     0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x64,
-     0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x73,
-     0x12, 0x4e, 0x0a, 0x23, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x74,
-     0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x62,
-     0x6f, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69,
-     0x6f, 0x6e, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x21, 0x63,
-     0x6f, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68,
-     0x65, 0x63, 0x6b, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x6e,
-     0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x15,
-     0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x6d,
-     0x69, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x10, 0x20,
-     0x01, 0x28, 0x05, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-     0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x12, 0x63, 0x0a, 0x30, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x73, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63,
-     0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x61, 0x79, 0x65, 0x72,
-     0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
-     0x73, 0x69, 0x6e, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x29,
-     0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x57, 0x69, 0x74, 0x68, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4c,
-     0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x53, 0x69, 0x6e, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x65,
-     0x64, 0x73, 0x5f, 0x72, 0x65, 0x64, 0x72, 0x61, 0x77, 0x18, 0x12, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x0b, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65,
-     0x64, 0x72, 0x61, 0x77, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x65, 0x65, 0x64,
-     0x73, 0x5f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69,
-     0x6c, 0x65, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e,
-     0x65, 0x65, 0x64, 0x73, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54,
-     0x69, 0x6c, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x6e, 0x65, 0x65, 0x64,
-     0x73, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08,
-     0x52, 0x13, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x42, 0x65, 0x67, 0x69, 0x6e,
-     0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a,
-     0x1a, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x5f, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x6e,
-     0x65, 0x65, 0x64, 0x73, 0x4f, 0x6e, 0x65, 0x42, 0x65, 0x67, 0x69, 0x6e,
-     0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a,
-     0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x16, 0x20, 0x01,
-     0x28, 0x08, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12,
-     0x39, 0x0a, 0x19, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61,
-     0x6d, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61,
-     0x75, 0x73, 0x65, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16,
-     0x62, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f,
-     0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x75, 0x73, 0x65, 0x64, 0x12, 0x19,
-     0x0a, 0x08, 0x63, 0x61, 0x6e, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x18,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x61, 0x6e, 0x44, 0x72, 0x61,
-     0x77, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x19,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x44, 0x72, 0x61, 0x77, 0x12, 0x28,
-     0x0a, 0x10, 0x68, 0x61, 0x73, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
-     0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08,
-     0x52, 0x0e, 0x68, 0x61, 0x73, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-     0x54, 0x72, 0x65, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x70, 0x65, 0x6e, 0x64,
-     0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f,
-     0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x61, 0x63,
-     0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x1b, 0x20, 0x01,
-     0x28, 0x08, 0x52, 0x1f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54,
-     0x72, 0x65, 0x65, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f,
-     0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-     0x3e, 0x0a, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x72,
-     0x65, 0x65, 0x5f, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x66, 0x69, 0x72,
-     0x73, 0x74, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x1c, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x65,
-     0x65, 0x4e, 0x65, 0x65, 0x64, 0x73, 0x46, 0x69, 0x72, 0x73, 0x74, 0x44,
-     0x72, 0x61, 0x77, 0x12, 0x3d, 0x0a, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x76,
-     0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x72, 0x65,
-     0x61, 0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18,
-     0x1d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76,
-     0x65, 0x54, 0x72, 0x65, 0x65, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79,
-     0x54, 0x6f, 0x44, 0x72, 0x61, 0x77, 0x12, 0x6c, 0x0a, 0x35, 0x64, 0x69,
-     0x64, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x6e, 0x64,
-     0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f,
-     0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f,
+     0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x52, 0x13, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x49,
+     0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x12, 0x81, 0x01, 0x0a, 0x16, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f,
+     0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73,
+     0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x4c,
+     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43,
+     0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61,
+     0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x52, 0x13, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4d,
+     0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x12, 0x8e, 0x01, 0x0a, 0x1b, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f,
      0x74, 0x72, 0x65, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73,
-     0x69, 0x6e, 0x6b, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2d, 0x64,
-     0x69, 0x64, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x49,
-     0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x46, 0x69, 0x72,
-     0x73, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46,
-     0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x12, 0x6a, 0x0a, 0x0d,
-     0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
-     0x79, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x45, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70,
-     0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d,
-     0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x69, 0x6e, 0x6f, 0x72,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x50, 0x72,
-     0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x0c, 0x74, 0x72, 0x65, 0x65,
-     0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x7d, 0x0a, 0x14,
-     0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c,
-     0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x20, 0x20, 0x01,
-     0x28, 0x0e, 0x32, 0x4b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
-     0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
-     0x65, 0x2e, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x2e, 0x53, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c,
-     0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x12, 0x73, 0x63, 0x72,
-     0x6f, 0x6c, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x12, 0x5d, 0x0a, 0x2d, 0x63, 0x72, 0x69, 0x74, 0x69,
-     0x63, 0x61, 0x6c, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61,
-     0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f,
-     0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x73, 0x5f,
-     0x66, 0x61, 0x73, 0x74, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x52, 0x26,
-     0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x42, 0x65, 0x67, 0x69,
-     0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x6f,
-     0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x73, 0x46, 0x61,
-     0x73, 0x74, 0x12, 0x46, 0x0a, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x65, 0x64,
-     0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69,
-     0x6e, 0x65, 0x18, 0x22, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x6d, 0x61,
-     0x69, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x4d, 0x69, 0x73, 0x73,
-     0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69,
-     0x6e, 0x65, 0x12, 0x5b, 0x0a, 0x2c, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6e,
-     0x65, 0x78, 0x74, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61,
-     0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f,
-     0x72, 0x65, 0x64, 0x75, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e,
-     0x63, 0x79, 0x18, 0x23, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x73, 0x6b,
-     0x69, 0x70, 0x4e, 0x65, 0x78, 0x74, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d,
-     0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x52, 0x65,
-     0x64, 0x75, 0x63, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12,
-     0x37, 0x0a, 0x18, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, 0x6e, 0x65, 0x65,
-     0x64, 0x73, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61,
-     0x6d, 0x65, 0x73, 0x18, 0x24, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x76,
-     0x69, 0x64, 0x65, 0x6f, 0x4e, 0x65, 0x65, 0x64, 0x73, 0x42, 0x65, 0x67,
-     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x16,
-     0x64, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f,
-     0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x25,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x65, 0x66, 0x65, 0x72, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x12, 0x3a, 0x0a, 0x1a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x6f,
-     0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, 0x64, 0x5f, 0x6e, 0x6f, 0x5f,
-     0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x26, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69,
-     0x74, 0x48, 0x61, 0x64, 0x4e, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
-     0x73, 0x12, 0x32, 0x0a, 0x16, 0x64, 0x69, 0x64, 0x5f, 0x64, 0x72, 0x61,
-     0x77, 0x5f, 0x69, 0x6e, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x18, 0x27, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x64,
-     0x69, 0x64, 0x44, 0x72, 0x61, 0x77, 0x49, 0x6e, 0x4c, 0x61, 0x73, 0x74,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x18, 0x64, 0x69, 0x64,
-     0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6c,
-     0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x64, 0x53, 0x75, 0x62, 0x6d,
-     0x69, 0x74, 0x49, 0x6e, 0x4c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x69,
-     0x6d, 0x70, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x76,
-     0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x29, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x19, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x49, 0x6d,
-     0x70, 0x6c, 0x53, 0x69, 0x64, 0x65, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69,
-     0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x21, 0x63, 0x75,
-     0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
-     0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x69, 0x6d,
-     0x70, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x2a, 0x20, 0x01, 0x28,
-     0x08, 0x52, 0x1c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x65,
-     0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65, 0x65, 0x49, 0x73, 0x49,
-     0x6d, 0x70, 0x6c, 0x53, 0x69, 0x64, 0x65, 0x12, 0x4b, 0x0a, 0x23, 0x70,
-     0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x65, 0x6e, 0x64,
-     0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x77, 0x61, 0x73,
-     0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x2b,
-     0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f,
-     0x75, 0x73, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65,
-     0x65, 0x57, 0x61, 0x73, 0x49, 0x6d, 0x70, 0x6c, 0x53, 0x69, 0x64, 0x65,
-     0x12, 0x5f, 0x0a, 0x2d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69,
-     0x6e, 0x67, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66, 0x6f,
-     0x72, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x72, 0x65,
-     0x65, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x28, 0x70, 0x72, 0x6f,
+     0x69, 0x6e, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20,
+     0x01, 0x28, 0x0e, 0x32, 0x50, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68,
+     0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74,
+     0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
+     0x6e, 0x65, 0x2e, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x52, 0x17, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x63, 0x65,
+     0x64, 0x5f, 0x72, 0x65, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x74, 0x61,
+     0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x53, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d,
+     0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
+     0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x61, 0x6a, 0x6f,
+     0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65,
+     0x64, 0x52, 0x65, 0x64, 0x72, 0x61, 0x77, 0x4f, 0x6e, 0x54, 0x69, 0x6d,
+     0x65, 0x6f, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x11, 0x66,
+     0x6f, 0x72, 0x63, 0x65, 0x64, 0x52, 0x65, 0x64, 0x72, 0x61, 0x77, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x22, 0xa1, 0x01, 0x0a, 0x13, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49,
+     0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
+     0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+     0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f,
+     0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x49,
+     0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x27, 0x0a, 0x23, 0x42, 0x45, 0x47,
+     0x49, 0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x46, 0x52, 0x41, 0x4d,
+     0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x42, 0x45, 0x47,
+     0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x24,
+     0x0a, 0x20, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x49, 0x4d, 0x50, 0x4c,
+     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x49, 0x44,
+     0x45, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x03,
+     0x22, 0x93, 0x01, 0x0a, 0x13, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61,
+     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65,
+     0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41,
+     0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x53,
+     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19,
+     0x0a, 0x15, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e,
+     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10,
+     0x01, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d,
+     0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x53, 0x45,
+     0x4e, 0x54, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x42, 0x45, 0x47, 0x49,
+     0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
+     0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x54, 0x4f, 0x5f, 0x43, 0x4f,
+     0x4d, 0x4d, 0x49, 0x54, 0x10, 0x03, 0x22, 0xf4, 0x01, 0x0a, 0x17, 0x4c,
+     0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72, 0x61, 0x6d,
+     0x65, 0x53, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20,
+     0x0a, 0x1c, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45,
+     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+     0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15,
+     0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46,
+     0x52, 0x41, 0x4d, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12,
+     0x1b, 0x0a, 0x17, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45,
+     0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49,
+     0x56, 0x45, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x4c, 0x41, 0x59, 0x45,
+     0x52, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
+     0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12,
+     0x2d, 0x0a, 0x29, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45,
+     0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x57, 0x41, 0x49, 0x54,
+     0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x52, 0x53,
+     0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x12, 0x31,
+     0x0a, 0x2d, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45,
+     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x49,
+     0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x52, 0x53, 0x54,
+     0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10,
+     0x05, 0x22, 0xc7, 0x01, 0x0a, 0x1a, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x64,
+     0x52, 0x65, 0x64, 0x72, 0x61, 0x77, 0x4f, 0x6e, 0x54, 0x69, 0x6d, 0x65,
+     0x6f, 0x75, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x19,
+     0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41,
+     0x57, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
+     0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x46, 0x4f, 0x52, 0x43, 0x45,
+     0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x49, 0x44, 0x4c,
+     0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x4f, 0x52, 0x43, 0x45,
+     0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x57, 0x41, 0x49,
+     0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x4d,
+     0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x28, 0x0a, 0x24, 0x46, 0x4f, 0x52,
+     0x43, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x57,
+     0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x12,
+     0x22, 0x0a, 0x1e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x52, 0x45,
+     0x44, 0x52, 0x41, 0x57, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47,
+     0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x10, 0x04, 0x1a,
+     0xb3, 0x1b, 0x0a, 0x0a, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
+     0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x75, 0x6e,
+     0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65,
+     0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x75, 0x72,
+     0x72, 0x65, 0x6e, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d,
+     0x62, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x22, 0x6c, 0x61, 0x73, 0x74, 0x5f,
+     0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+     0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x66,
+     0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+     0x1e, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75,
+     0x6d, 0x62, 0x65, 0x72, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x50, 0x65,
+     0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x20, 0x6c,
+     0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75,
+     0x6d, 0x62, 0x65, 0x72, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x70, 0x65,
+     0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
+     0x05, 0x52, 0x1c, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65,
+     0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x44, 0x72, 0x61, 0x77, 0x50, 0x65,
+     0x72, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x27, 0x6c,
+     0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x75,
+     0x6d, 0x62, 0x65, 0x72, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d,
+     0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65,
+     0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x21, 0x6c, 0x61,
+     0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65,
+     0x72, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72,
+     0x61, 0x6d, 0x65, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64,
+     0x69, 0x64, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x07, 0x64, 0x69, 0x64, 0x44, 0x72, 0x61, 0x77, 0x12, 0x59,
+     0x0a, 0x2b, 0x64, 0x69, 0x64, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x62,
+     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72,
+     0x61, 0x6d, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x63, 0x75, 0x72, 0x72,
+     0x65, 0x6e, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x24, 0x64, 0x69, 0x64, 0x53, 0x65, 0x6e, 0x64,
+     0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x46, 0x6f, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x5f, 0x0a, 0x2e, 0x64, 0x69, 0x64,
+     0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x62, 0x65, 0x67, 0x69,
+     0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
+     0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65,
+     0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x27, 0x64, 0x69, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79,
+     0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65,
+     0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x12, 0x5d, 0x0a, 0x2d, 0x64, 0x69,
+     0x64, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x62, 0x65, 0x67,
+     0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
+     0x65, 0x64, 0x5f, 0x73, 0x6f, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x26, 0x64, 0x69, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79,
+     0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x4e, 0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65,
+     0x64, 0x53, 0x6f, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x23, 0x77, 0x61, 0x6e,
+     0x74, 0x73, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69,
+     0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f,
+     0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x1e, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4e,
+     0x6f, 0x74, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x35,
+     0x0a, 0x17, 0x64, 0x69, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+     0x5f, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x64,
+     0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x75, 0x72, 0x69, 0x6e, 0x67,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x64, 0x69, 0x64,
+     0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f,
+     0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x6e, 0x6b, 0x18, 0x0c, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x1f, 0x64, 0x69, 0x64, 0x49, 0x6e, 0x76, 0x61,
+     0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x54,
+     0x72, 0x65, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b,
+     0x12, 0x48, 0x0a, 0x21, 0x64, 0x69, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x66,
+     0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x73, 0x69, 0x64,
+     0x65, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x69, 0x6f,
+     0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x64, 0x69, 0x64,
+     0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x49, 0x6d, 0x70, 0x6c, 0x53,
+     0x69, 0x64, 0x65, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x69,
+     0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x64, 0x69, 0x64, 0x5f, 0x70, 0x72,
+     0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x18,
+     0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x64, 0x50, 0x72,
+     0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x4e,
+     0x0a, 0x23, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x74, 0x69, 0x76,
+     0x65, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x62, 0x6f, 0x61,
+     0x72, 0x64, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+     0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x21, 0x63, 0x6f, 0x6e,
+     0x73, 0x65, 0x63, 0x75, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x65, 0x63,
+     0x6b, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x6e, 0x69, 0x6d,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65,
+     0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28,
+     0x05, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x75,
+     0x62, 0x6d, 0x69, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x63,
+     0x0a, 0x30, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x72, 0x61,
+     0x6d, 0x65, 0x73, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x75, 0x72,
+     0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x74,
+     0x72, 0x65, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69,
+     0x6e, 0x6b, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x29, 0x73, 0x75,
+     0x62, 0x6d, 0x69, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x57, 0x69,
+     0x74, 0x68, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4c, 0x61, 0x79,
+     0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
+     0x69, 0x6e, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x65, 0x64, 0x73,
+     0x5f, 0x72, 0x65, 0x64, 0x72, 0x61, 0x77, 0x18, 0x12, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x0b, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x52, 0x65, 0x64, 0x72,
+     0x61, 0x77, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f,
+     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6c, 0x65,
+     0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x65, 0x65,
+     0x64, 0x73, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x69, 0x6c,
+     0x65, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f,
+     0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13,
+     0x6e, 0x65, 0x65, 0x64, 0x73, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61,
+     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x1a, 0x6e,
+     0x65, 0x65, 0x64, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x5f, 0x62, 0x65, 0x67,
+     0x69, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x6e, 0x65, 0x65,
+     0x64, 0x73, 0x4f, 0x6e, 0x65, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d,
+     0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76,
+     0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08,
+     0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x39, 0x0a,
+     0x19, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
+     0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x75, 0x73,
+     0x65, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x62, 0x65,
+     0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75, 0x72,
+     0x63, 0x65, 0x50, 0x61, 0x75, 0x73, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08,
+     0x63, 0x61, 0x6e, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x18, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x07, 0x63, 0x61, 0x6e, 0x44, 0x72, 0x61, 0x77, 0x12,
+     0x2b, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x6c,
+     0x65, 0x73, 0x73, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x19, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+     0x6c, 0x65, 0x73, 0x73, 0x44, 0x72, 0x61, 0x77, 0x12, 0x28, 0x0a, 0x10,
+     0x68, 0x61, 0x73, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f,
+     0x74, 0x72, 0x65, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e,
+     0x68, 0x61, 0x73, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72,
+     0x65, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
+     0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x72, 0x65,
+     0x61, 0x64, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x74, 0x69,
+     0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08,
+     0x52, 0x1f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65,
+     0x65, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x41,
+     0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a,
+     0x1c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
+     0x5f, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74,
+     0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x65, 0x65, 0x4e,
+     0x65, 0x65, 0x64, 0x73, 0x46, 0x69, 0x72, 0x73, 0x74, 0x44, 0x72, 0x61,
+     0x77, 0x12, 0x3d, 0x0a, 0x1c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f,
+     0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64,
+     0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x18, 0x1d, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54,
+     0x72, 0x65, 0x65, 0x49, 0x73, 0x52, 0x65, 0x61, 0x64, 0x79, 0x54, 0x6f,
+     0x44, 0x72, 0x61, 0x77, 0x12, 0x6c, 0x0a, 0x35, 0x64, 0x69, 0x64, 0x5f,
+     0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x69,
+     0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x66, 0x69,
+     0x72, 0x73, 0x74, 0x5f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x74, 0x72,
+     0x65, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x6e,
+     0x6b, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2d, 0x64, 0x69, 0x64,
+     0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x6e, 0x64, 0x49, 0x6e, 0x69,
+     0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74,
+     0x4c, 0x61, 0x79, 0x65, 0x72, 0x54, 0x72, 0x65, 0x65, 0x46, 0x72, 0x61,
+     0x6d, 0x65, 0x53, 0x69, 0x6e, 0x6b, 0x12, 0x6a, 0x0a, 0x0d, 0x74, 0x72,
+     0x65, 0x65, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18,
+     0x1f, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x45, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73,
+     0x69, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63,
+     0x68, 0x69, 0x6e, 0x65, 0x2e, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74,
+     0x61, 0x74, 0x65, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x50, 0x72, 0x69, 0x6f,
+     0x72, 0x69, 0x74, 0x79, 0x52, 0x0c, 0x74, 0x72, 0x65, 0x65, 0x50, 0x72,
+     0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x7d, 0x0a, 0x14, 0x73, 0x63,
+     0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
+     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0e,
+     0x32, 0x4b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e,
+     0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x53,
+     0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
+     0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x12, 0x73, 0x63, 0x72, 0x6f, 0x6c,
+     0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x12, 0x5d, 0x0a, 0x2d, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61,
+     0x6c, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x61, 0x63,
+     0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x66, 0x61,
+     0x73, 0x74, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x52, 0x26, 0x63, 0x72,
+     0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d,
+     0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x41, 0x63,
+     0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x73, 0x46, 0x61, 0x73, 0x74,
+     0x12, 0x46, 0x0a, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68, 0x72,
+     0x65, 0x61, 0x64, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x6c,
+     0x61, 0x73, 0x74, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65,
+     0x18, 0x22, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x6d, 0x61, 0x69, 0x6e,
+     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x65, 0x64,
+     0x4c, 0x61, 0x73, 0x74, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65,
+     0x12, 0x5b, 0x0a, 0x2c, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6e, 0x65, 0x78,
+     0x74, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x72, 0x65,
+     0x64, 0x75, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79,
+     0x18, 0x23, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x73, 0x6b, 0x69, 0x70,
+     0x4e, 0x65, 0x78, 0x74, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69,
+     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x52, 0x65, 0x64, 0x75,
+     0x63, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x37, 0x0a,
+     0x18, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, 0x6e, 0x65, 0x65, 0x64, 0x73,
+     0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
+     0x73, 0x18, 0x24, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x76, 0x69, 0x64,
+     0x65, 0x6f, 0x4e, 0x65, 0x65, 0x64, 0x73, 0x42, 0x65, 0x67, 0x69, 0x6e,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x65,
+     0x66, 0x65, 0x72, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61,
+     0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x25, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x13, 0x64, 0x65, 0x66, 0x65, 0x72, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
+     0x3a, 0x0a, 0x1a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
+     0x69, 0x74, 0x5f, 0x68, 0x61, 0x64, 0x5f, 0x6e, 0x6f, 0x5f, 0x75, 0x70,
+     0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x26, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x16, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48,
+     0x61, 0x64, 0x4e, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12,
+     0x32, 0x0a, 0x16, 0x64, 0x69, 0x64, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f,
+     0x69, 0x6e, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x18, 0x27, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x64, 0x69, 0x64,
+     0x44, 0x72, 0x61, 0x77, 0x49, 0x6e, 0x4c, 0x61, 0x73, 0x74, 0x46, 0x72,
+     0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x18, 0x64, 0x69, 0x64, 0x5f, 0x73,
+     0x75, 0x62, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6c, 0x61, 0x73,
+     0x74, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x14, 0x64, 0x69, 0x64, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
+     0x49, 0x6e, 0x4c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
+     0x3f, 0x0a, 0x1c, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x69, 0x6d, 0x70,
+     0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x76, 0x61, 0x6c,
+     0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x29, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x19, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x49, 0x6d, 0x70, 0x6c,
+     0x53, 0x69, 0x64, 0x65, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
+     0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x21, 0x63, 0x75, 0x72, 0x72,
+     0x65, 0x6e, 0x74, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f,
+     0x74, 0x72, 0x65, 0x65, 0x5f, 0x69, 0x73, 0x5f, 0x69, 0x6d, 0x70, 0x6c,
+     0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x08, 0x52,
+     0x1c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x65, 0x6e, 0x64,
+     0x69, 0x6e, 0x67, 0x54, 0x72, 0x65, 0x65, 0x49, 0x73, 0x49, 0x6d, 0x70,
+     0x6c, 0x53, 0x69, 0x64, 0x65, 0x12, 0x4b, 0x0a, 0x23, 0x70, 0x72, 0x65,
+     0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
+     0x67, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x77, 0x61, 0x73, 0x5f, 0x69,
+     0x6d, 0x70, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x2b, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x1e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73,
+     0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65, 0x65, 0x57,
+     0x61, 0x73, 0x49, 0x6d, 0x70, 0x6c, 0x53, 0x69, 0x64, 0x65, 0x12, 0x5f,
+     0x0a, 0x2d, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67,
+     0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77,
+     0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x5f,
+     0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18,
+     0x2c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x28, 0x70, 0x72, 0x6f, 0x63, 0x65,
+     0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69,
+     0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x46, 0x6f,
+     0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x65, 0x65, 0x12,
+     0x61, 0x0a, 0x2e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e,
+     0x67, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+     0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66, 0x6f, 0x72,
+     0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x65,
+     0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x29, 0x70, 0x72, 0x6f,
      0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x69, 0x6d, 0x61,
      0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73,
-     0x46, 0x6f, 0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x65,
-     0x65, 0x12, 0x61, 0x0a, 0x2e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f,
-     0x6e, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66,
-     0x6f, 0x72, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74,
-     0x72, 0x65, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x29, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x69,
-     0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x65,
-     0x74, 0x73, 0x46, 0x6f, 0x72, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-     0x54, 0x72, 0x65, 0x65, 0x12, 0x59, 0x0a, 0x2a, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74,
-     0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66, 0x6f,
-     0x72, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72,
-     0x65, 0x65, 0x18, 0x2e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x6e,
-     0x74, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x46, 0x6f, 0x72,
-     0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65, 0x65, 0x22,
-     0xb8, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x65, 0x65, 0x50, 0x72, 0x69, 0x6f,
-     0x72, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x52, 0x45, 0x45,
-     0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e,
+     0x46, 0x6f, 0x72, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72,
+     0x65, 0x65, 0x12, 0x59, 0x0a, 0x2a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
+     0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x5f, 0x77,
+     0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x5f,
+     0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x72, 0x65, 0x65,
+     0x18, 0x2e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x70, 0x72, 0x6f, 0x63,
+     0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x6e, 0x74, 0x57,
+     0x6f, 0x72, 0x6b, 0x6c, 0x65, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x50, 0x65,
+     0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x65, 0x65, 0x22, 0xb8, 0x01,
+     0x0a, 0x0c, 0x54, 0x72, 0x65, 0x65, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69,
+     0x74, 0x79, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x50,
+     0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+     0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2e, 0x0a,
+     0x2a, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49,
+     0x54, 0x59, 0x5f, 0x53, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f,
+     0x52, 0x49, 0x54, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54,
+     0x48, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x53, 0x10, 0x01, 0x12, 0x2b, 0x0a,
+     0x27, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49,
+     0x54, 0x59, 0x5f, 0x53, 0x4d, 0x4f, 0x4f, 0x54, 0x48, 0x4e, 0x45, 0x53,
+     0x53, 0x5f, 0x54, 0x41, 0x4b, 0x45, 0x53, 0x5f, 0x50, 0x52, 0x49, 0x4f,
+     0x52, 0x49, 0x54, 0x59, 0x10, 0x02, 0x12, 0x2c, 0x0a, 0x28, 0x54, 0x52,
+     0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f,
+     0x4e, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f,
+     0x54, 0x41, 0x4b, 0x45, 0x53, 0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49,
+     0x54, 0x59, 0x10, 0x03, 0x22, 0x82, 0x01, 0x0a, 0x12, 0x53, 0x63, 0x72,
+     0x6f, 0x6c, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x74,
+     0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x43, 0x52, 0x4f, 0x4c,
+     0x4c, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x52, 0x5f, 0x55, 0x4e,
      0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
-     0x2e, 0x0a, 0x2a, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f,
-     0x52, 0x49, 0x54, 0x59, 0x5f, 0x53, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x52,
-     0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x5f, 0x42,
-     0x4f, 0x54, 0x48, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x53, 0x10, 0x01, 0x12,
-     0x2b, 0x0a, 0x27, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f,
-     0x52, 0x49, 0x54, 0x59, 0x5f, 0x53, 0x4d, 0x4f, 0x4f, 0x54, 0x48, 0x4e,
-     0x45, 0x53, 0x53, 0x5f, 0x54, 0x41, 0x4b, 0x45, 0x53, 0x5f, 0x50, 0x52,
-     0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x10, 0x02, 0x12, 0x2c, 0x0a, 0x28,
-     0x54, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54,
-     0x59, 0x5f, 0x4e, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e,
-     0x54, 0x5f, 0x54, 0x41, 0x4b, 0x45, 0x53, 0x5f, 0x50, 0x52, 0x49, 0x4f,
-     0x52, 0x49, 0x54, 0x59, 0x10, 0x03, 0x22, 0x82, 0x01, 0x0a, 0x12, 0x53,
-     0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x43, 0x52,
-     0x4f, 0x4c, 0x4c, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x52, 0x5f,
-     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
-     0x00, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f,
-     0x41, 0x46, 0x46, 0x45, 0x43, 0x54, 0x53, 0x5f, 0x53, 0x43, 0x52, 0x4f,
-     0x4c, 0x4c, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x52, 0x10, 0x01,
-     0x12, 0x29, 0x0a, 0x25, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x44,
-     0x4f, 0x45, 0x53, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x41, 0x46, 0x46, 0x45,
-     0x43, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x48, 0x41,
-     0x4e, 0x44, 0x4c, 0x45, 0x52, 0x10, 0x02, 0x22, 0x8f, 0x05, 0x0a, 0x0e,
+     0x21, 0x0a, 0x1d, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x41, 0x46,
+     0x46, 0x45, 0x43, 0x54, 0x53, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c,
+     0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x52, 0x10, 0x01, 0x12, 0x29,
+     0x0a, 0x25, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x44, 0x4f, 0x45,
+     0x53, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x41, 0x46, 0x46, 0x45, 0x43, 0x54,
+     0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x48, 0x41, 0x4e, 0x44,
+     0x4c, 0x45, 0x52, 0x10, 0x02, 0x22, 0x8f, 0x05, 0x0a, 0x0e, 0x42, 0x65,
+     0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73,
+     0x12, 0x46, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x0e, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x2e,
      0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72,
-     0x67, 0x73, 0x12, 0x46, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67,
-     0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x41, 0x72, 0x67, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79,
-     0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
-     0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73,
-     0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73,
-     0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62,
-     0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x65,
-     0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,
-     0x12, 0x22, 0x0a, 0x0d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x0b, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x73,
-     0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65,
-     0x5f, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64,
-     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x12, 0x2a, 0x0a,
-     0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x64, 0x65,
-     0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x44, 0x65,
-     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x6e, 0x5f,
-     0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74,
-     0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x6e, 0x43,
-     0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12,
-     0x21, 0x0a, 0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x6f,
-     0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61,
-     0x6e, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x30,
-     0x0a, 0x13, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63,
-     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x69, 0x64, 0x18, 0x09, 0x20,
-     0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63,
-     0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x69, 0x64,
-     0x12, 0x4a, 0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c,
-     0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00,
-     0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61,
-     0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x42, 0x65, 0x67,
-     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x54,
-     0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x21, 0x42, 0x45, 0x47, 0x49, 0x4e,
-     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53, 0x5f,
-     0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
-     0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x21, 0x0a, 0x1d, 0x42, 0x45,
-     0x47, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52,
-     0x47, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41,
-     0x4c, 0x49, 0x44, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47,
-     0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47,
-     0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41,
-     0x4c, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49, 0x4e,
-     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53, 0x5f,
-     0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x45, 0x44, 0x10,
-     0x03, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
-     0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0xf5, 0x05, 0x0a, 0x12, 0x42, 0x65,
-     0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x41, 0x72, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61,
-     0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
-     0x41, 0x74, 0x55, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x69,
-     0x73, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68,
-     0x65, 0x64, 0x41, 0x74, 0x55, 0x73, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74,
-     0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70,
-     0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x2e, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12,
-     0x44, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61,
-     0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x75,
-     0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12, 0x3e, 0x0a,
-     0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42,
-     0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67,
-     0x73, 0x48, 0x00, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x72, 0x67,
-     0x73, 0x12, 0x5c, 0x0a, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-     0x6d, 0x70, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65,
-     0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x41, 0x72, 0x67, 0x73, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-     0x6d, 0x70, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x52, 0x0e, 0x74, 0x69, 0x6d,
-     0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x1a,
-     0xad, 0x02, 0x0a, 0x0e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-     0x70, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65,
-     0x72, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x31, 0x0a,
-     0x15, 0x6e, 0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x61, 0x64,
-     0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6e, 0x6f, 0x77, 0x54, 0x6f, 0x44,
-     0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61,
-     0x12, 0x34, 0x0a, 0x17, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x77, 0x5f, 0x64, 0x65,
-     0x6c, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x4e, 0x6f,
-     0x77, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x3e, 0x0a, 0x1c, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f,
-     0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c,
-     0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x44, 0x65, 0x61,
-     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x10,
-     0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x03, 0x6e, 0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x6d,
-     0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
-     0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18,
-     0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c,
-     0x69, 0x6e, 0x65, 0x22, 0x38, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x12, 0x18, 0x0a, 0x14, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46, 0x52,
-     0x41, 0x4d, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44,
-     0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f,
-     0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x55, 0x53, 0x49, 0x4e, 0x47, 0x10,
-     0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x22, 0xa6, 0x01,
-     0x0a, 0x17, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
-     0x65, 0x12, 0x37, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64,
-     0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x15, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x42, 0x65, 0x67, 0x69,
-     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x12, 0x52,
-     0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72,
-     0x67, 0x73, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x42, 0x65, 0x67, 0x69,
-     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x22, 0xc5,
-     0x01, 0x0a, 0x15, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65,
+     0x67, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
      0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69,
-     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x75,
-     0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70,
-     0x61, 0x75, 0x73, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x75, 0x6d,
-     0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x62, 0x73,
-     0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, 0x15, 0x6c, 0x61,
-     0x73, 0x74, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61,
-     0x6d, 0x65, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x6f, 0x75,
+     0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71,
+     0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+     0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75,
+     0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x22,
+     0x0a, 0x0d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+     0x5f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x73, 0x12, 0x1f,
+     0x0a, 0x0b, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75,
+     0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x61,
+     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x69,
+     0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74,
+     0x61, 0x5f, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f,
+     0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x74,
+     0x61, 0x55, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x6e, 0x5f, 0x63, 0x72,
+     0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18,
+     0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6f, 0x6e, 0x43, 0x72, 0x69,
+     0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x21, 0x0a,
+     0x0c, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x6e, 0x6c,
+     0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6e, 0x69,
+     0x6d, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x13,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74,
+     0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28,
+     0x04, 0x48, 0x00, 0x52, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c,
+     0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x69, 0x64, 0x12, 0x4a,
+     0x0a, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+     0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0e,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69,
+     0x6f, 0x6e, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x42, 0x65, 0x67, 0x69, 0x6e,
+     0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x54, 0x79, 0x70,
+     0x65, 0x12, 0x25, 0x0a, 0x21, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46,
+     0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53, 0x5f, 0x54, 0x59,
+     0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+     0x45, 0x44, 0x10, 0x00, 0x12, 0x21, 0x0a, 0x1d, 0x42, 0x45, 0x47, 0x49,
+     0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53,
+     0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
+     0x44, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49, 0x4e,
+     0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53, 0x5f,
+     0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10,
+     0x02, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46,
+     0x52, 0x41, 0x4d, 0x45, 0x5f, 0x41, 0x52, 0x47, 0x53, 0x5f, 0x54, 0x59,
+     0x50, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x45, 0x44, 0x10, 0x03, 0x42,
+     0x0e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66,
+     0x72, 0x6f, 0x6d, 0x22, 0xf5, 0x05, 0x0a, 0x12, 0x42, 0x65, 0x67, 0x69,
+     0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72,
+     0x67, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
+     0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
+     0x55, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68,
+     0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x0c, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64,
+     0x41, 0x74, 0x55, 0x73, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
+     0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x2e, 0x53, 0x74, 0x61,
+     0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x44, 0x0a,
+     0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x72, 0x67,
+     0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65,
+     0x41, 0x72, 0x67, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72,
+     0x65, 0x6e, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x09, 0x6c,
+     0x61, 0x73, 0x74, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01,
+     0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67,
+     0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x48,
+     0x00, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x12,
+     0x5c, 0x0a, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+     0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
      0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69,
-     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x52, 0x12,
-     0x6c, 0x61, 0x73, 0x74, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x22, 0xfd, 0x04, 0x0a, 0x17, 0x43,
-     0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x54, 0x69, 0x6d,
-     0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x65,
-     0x0a, 0x31, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65,
-     0x5f, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x65, 0x73,
-     0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
-     0x5f, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x2a, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x51, 0x75, 0x65, 0x75, 0x65, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63,
+     0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72,
+     0x67, 0x73, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+     0x73, 0x49, 0x6e, 0x55, 0x73, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x73,
+     0x74, 0x61, 0x6d, 0x70, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x1a, 0xad, 0x02,
+     0x0a, 0x0e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73,
+     0x49, 0x6e, 0x55, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65,
+     0x72, 0x76, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01,
+     0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76,
+     0x61, 0x6c, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x15, 0x6e,
+     0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69,
+     0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x12, 0x6e, 0x6f, 0x77, 0x54, 0x6f, 0x44, 0x65, 0x61,
+     0x64, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x34,
+     0x0a, 0x17, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+     0x5f, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x74,
+     0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x66, 0x72, 0x61,
+     0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x4e, 0x6f, 0x77, 0x44,
+     0x65, 0x6c, 0x74, 0x61, 0x12, 0x3e, 0x0a, 0x1c, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65,
+     0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
+     0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x66, 0x72, 0x61, 0x6d,
+     0x65, 0x54, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x44, 0x65, 0x61, 0x64, 0x6c,
+     0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03,
+     0x6e, 0x6f, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6e,
+     0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
+     0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
+     0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
+     0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20,
+     0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e,
+     0x65, 0x22, 0x38, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18,
+     0x0a, 0x14, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d,
+     0x45, 0x5f, 0x46, 0x49, 0x4e, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x00,
+     0x12, 0x15, 0x0a, 0x11, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x46, 0x52,
+     0x41, 0x4d, 0x45, 0x5f, 0x55, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x42,
+     0x06, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x17,
+     0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4f, 0x62,
+     0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
+     0x37, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x62,
+     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x61,
+     0x72, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x64,
+     0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x12, 0x52, 0x0a, 0x15,
+     0x6c, 0x61, 0x73, 0x74, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65,
+     0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73,
+     0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x22, 0xc5, 0x01, 0x0a,
+     0x15, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b,
+     0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63,
+     0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x75, 0x73, 0x65,
+     0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x61, 0x75,
+     0x73, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x75, 0x6d, 0x5f, 0x6f,
+     0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x62, 0x73, 0x65, 0x72,
+     0x76, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74,
+     0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
+     0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46,
+     0x72, 0x61, 0x6d, 0x65, 0x41, 0x72, 0x67, 0x73, 0x52, 0x12, 0x6c, 0x61,
+     0x73, 0x74, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65,
+     0x41, 0x72, 0x67, 0x73, 0x22, 0xfd, 0x04, 0x0a, 0x17, 0x43, 0x6f, 0x6d,
+     0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x69, 0x6e,
+     0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x65, 0x0a, 0x31,
+     0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x63,
+     0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x65, 0x73, 0x74, 0x69,
+     0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75,
+     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x2a, 0x62, 0x65, 0x67,
+     0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x51,
+     0x75, 0x65, 0x75, 0x65, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
+     0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x74,
+     0x61, 0x55, 0x73, 0x12, 0x6c, 0x0a, 0x35, 0x62, 0x65, 0x67, 0x69, 0x6e,
+     0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
+     0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x72,
+     0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d,
+     0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x2d, 0x62, 0x65, 0x67, 0x69,
+     0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x51, 0x75,
+     0x65, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63,
      0x61, 0x6c, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65,
-     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x6c, 0x0a, 0x35, 0x62, 0x65, 0x67,
+     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x76, 0x0a, 0x3b, 0x62, 0x65, 0x67,
      0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x6d,
-     0x65, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f,
-     0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x65, 0x73, 0x74,
-     0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f,
-     0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x2d, 0x62, 0x65,
-     0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x51, 0x75, 0x65, 0x75, 0x65, 0x4e, 0x6f, 0x74, 0x43, 0x72, 0x69, 0x74,
-     0x69, 0x63, 0x61, 0x6c, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65,
-     0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x76, 0x0a, 0x3b, 0x62,
-     0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6f,
-     0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f,
-     0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74,
-     0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x31, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4d,
-     0x61, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72,
-     0x74, 0x54, 0x6f, 0x52, 0x65, 0x61, 0x64, 0x79, 0x54, 0x6f, 0x43, 0x6f,
-     0x6d, 0x6d, 0x69, 0x74, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65,
-     0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x5d, 0x0a, 0x2d, 0x63,
-     0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x61,
-     0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
+     0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x72,
+     0x65, 0x61, 0x64, 0x79, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
+     0x69, 0x74, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f,
+     0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x31, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4d, 0x61, 0x69,
+     0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54,
+     0x6f, 0x52, 0x65, 0x61, 0x64, 0x79, 0x54, 0x6f, 0x43, 0x6f, 0x6d, 0x6d,
+     0x69, 0x74, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65,
+     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x5d, 0x0a, 0x2d, 0x63, 0x6f, 0x6d,
+     0x6d, 0x69, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79,
+     0x5f, 0x74, 0x6f, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65,
+     0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65,
+     0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
+     0x52, 0x26, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x6f, 0x52, 0x65,
+     0x61, 0x64, 0x79, 0x54, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
+     0x65, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c,
+     0x74, 0x61, 0x55, 0x73, 0x12, 0x44, 0x0a, 0x1f, 0x70, 0x72, 0x65, 0x70,
+     0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x65, 0x73,
+     0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
+     0x5f, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x70,
+     0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x73, 0x45,
+     0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61,
+     0x55, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
      0x74, 0x65, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f,
-     0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x26, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x54, 0x6f,
-     0x52, 0x65, 0x61, 0x64, 0x79, 0x54, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76,
-     0x61, 0x74, 0x65, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44,
-     0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x44, 0x0a, 0x1f, 0x70, 0x72,
-     0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6c, 0x65, 0x73, 0x5f,
+     0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65,
+     0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x74,
+     0x61, 0x55, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x61, 0x77, 0x5f,
      0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c,
-     0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x1b, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x69, 0x6c, 0x65,
-     0x73, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c,
-     0x74, 0x61, 0x55, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74,
-     0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x06,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
-     0x74, 0x65, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x44, 0x65,
-     0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x61,
-     0x77, 0x5f, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x64,
-     0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x13, 0x64, 0x72, 0x61, 0x77, 0x45, 0x73, 0x74, 0x69, 0x6d,
-     0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x2a, 0xb0,
-     0x05, 0x0a, 0x1f, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d,
-     0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64,
-     0x75, 0x6c, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23,
-     0x0a, 0x1f, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c,
-     0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e,
-     0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
-     0x1c, 0x0a, 0x18, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55,
-     0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e,
-     0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x2d, 0x0a, 0x29, 0x43, 0x43, 0x5f,
-     0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43,
-     0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x42, 0x45,
-     0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41,
-     0x4d, 0x45, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x43, 0x5f, 0x53,
-     0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54,
-     0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x03,
-     0x12, 0x2a, 0x0a, 0x26, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44,
-     0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
-     0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e,
-     0x43, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x10, 0x04, 0x12, 0x28, 0x0a, 0x24,
+     0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
+     0x13, 0x64, 0x72, 0x61, 0x77, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74,
+     0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x2a, 0xb0, 0x05, 0x0a,
+     0x1f, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f,
+     0x73, 0x69, 0x74, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
+     0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x1f,
      0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52,
-     0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x52, 0x41, 0x57,
-     0x5f, 0x49, 0x46, 0x5f, 0x50, 0x4f, 0x53, 0x53, 0x49, 0x42, 0x4c, 0x45,
-     0x10, 0x05, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48,
-     0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f,
-     0x4e, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45,
-     0x44, 0x10, 0x06, 0x12, 0x22, 0x0a, 0x1e, 0x43, 0x43, 0x5f, 0x53, 0x43,
-     0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49,
-     0x4f, 0x4e, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x41, 0x42, 0x4f, 0x52,
-     0x54, 0x10, 0x07, 0x12, 0x3c, 0x0a, 0x38, 0x43, 0x43, 0x5f, 0x53, 0x43,
-     0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49,
-     0x4f, 0x4e, 0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4c, 0x41, 0x59,
-     0x45, 0x52, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d,
-     0x45, 0x5f, 0x53, 0x49, 0x4e, 0x4b, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54,
-     0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x25, 0x0a, 0x21, 0x43, 0x43, 0x5f,
-     0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43,
-     0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45,
-     0x5f, 0x54, 0x49, 0x4c, 0x45, 0x53, 0x10, 0x09, 0x12, 0x38, 0x0a, 0x34,
-     0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52,
-     0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41,
-     0x4c, 0x49, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x41, 0x59, 0x45, 0x52,
-     0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f,
-     0x53, 0x49, 0x4e, 0x4b, 0x10, 0x0a, 0x12, 0x36, 0x0a, 0x32, 0x43, 0x43,
-     0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41,
-     0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x45, 0x52, 0x46, 0x4f, 0x52,
-     0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f,
-     0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e,
-     0x10, 0x0b, 0x12, 0x42, 0x0a, 0x3e, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48,
-     0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f,
-     0x4e, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x5f, 0x42, 0x45, 0x47,
-     0x49, 0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d,
-     0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54,
-     0x45, 0x44, 0x5f, 0x55, 0x4e, 0x54, 0x49, 0x4c, 0x10, 0x0c, 0x12, 0x41,
-     0x0a, 0x3d, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c,
-     0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f,
-     0x54, 0x49, 0x46, 0x59, 0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d,
-     0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x4e, 0x4f,
-     0x54, 0x5f, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x53,
-     0x4f, 0x4f, 0x4e, 0x10, 0x0d, 0x0a, 0xb1, 0x04, 0x0a, 0x3d, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x70,
-     0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x13, 0x43, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f,
-     0x72, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d,
-     0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c,
-     0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x0e, 0x32, 0x34, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f,
-     0x72, 0x74, 0x65, 0x72, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x72,
-     0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65,
-     0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x61, 0x6d,
-     0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x0b, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20,
-     0x01, 0x28, 0x04, 0x52, 0x0d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x65,
-     0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x6d, 0x0a, 0x05, 0x53, 0x74,
-     0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x41, 0x54, 0x45,
-     0x5f, 0x4e, 0x4f, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x44,
-     0x45, 0x53, 0x49, 0x52, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13,
-     0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e,
-     0x54, 0x45, 0x44, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x1b, 0x0a,
-     0x17, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45,
-     0x4e, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x54, 0x49, 0x41, 0x4c,
-     0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f,
-     0x44, 0x52, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x03, 0x22, 0x7e, 0x0a,
-     0x0f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x65,
-     0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53,
-     0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
-     0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53,
-     0x4f, 0x4e, 0x5f, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x5f, 0x43,
-     0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x12,
-     0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x41,
-     0x49, 0x4e, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12,
-     0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4c,
-     0x49, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49,
-     0x54, 0x4f, 0x52, 0x10, 0x03, 0x0a, 0xb4, 0x01, 0x0a, 0x3f, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d,
-     0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x60, 0x0a, 0x15, 0x43, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61,
-     0x6d, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e,
-     0x61, 0x6d, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x48, 0x61, 0x73, 0x68,
-     0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
-     0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x0a, 0x79, 0x0a, 0x3c,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x65, 0x64, 0x5f, 0x73,
-     0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x28, 0x0a, 0x12, 0x43, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x4b, 0x65, 0x79, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76,
-     0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x0a,
-     0x89, 0x0d, 0x0a, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63,
-     0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
-     0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x61, 0x74,
-     0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xb8, 0x0c, 0x0a,
-     0x11, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e,
-     0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x3b, 0x0a,
-     0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
-     0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
-     0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x2e,
-     0x53, 0x74, 0x65, 0x70, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x2b,
-     0x0a, 0x12, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01,
-     0x28, 0x05, 0x52, 0x0f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x72, 0x65,
-     0x65, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x57, 0x0a, 0x0e, 0x63,
-     0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66,
-     0x6f, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65,
-     0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
-     0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x63,
-     0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f,
-     0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x61, 0x6c, 0x65,
-     0x73, 0x63, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b,
-     0x69, 0x73, 0x43, 0x6f, 0x61, 0x6c, 0x65, 0x73, 0x63, 0x65, 0x64, 0x12,
-     0x2a, 0x0a, 0x11, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73,
-     0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0f, 0x67, 0x65, 0x73, 0x74, 0x75, 0x72, 0x65, 0x53,
-     0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x49, 0x64, 0x1a, 0x88, 0x01, 0x0a, 0x0d,
-     0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
-     0x6f, 0x12, 0x5e, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65,
-     0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0e, 0x32, 0x37, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66,
-     0x6f, 0x2e, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6d,
-     0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d,
-     0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
-     0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x73,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x65,
-     0x55, 0x73, 0x22, 0xf2, 0x02, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12,
-     0x14, 0x0a, 0x10, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x55, 0x4e, 0x53, 0x50,
+     0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50,
      0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a,
-     0x18, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x49,
-     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55,
-     0x49, 0x10, 0x03, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x54, 0x45, 0x50, 0x5f,
-     0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54,
-     0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x10,
-     0x05, 0x12, 0x28, 0x0a, 0x24, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x44, 0x49,
-     0x44, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50,
-     0x55, 0x54, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x53,
-     0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x53,
-     0x54, 0x45, 0x50, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x5f, 0x49,
-     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4d,
-     0x41, 0x49, 0x4e, 0x10, 0x04, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x54, 0x45,
-     0x50, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x41,
-     0x44, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44,
-     0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x54, 0x45,
-     0x50, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50,
-     0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4d, 0x41, 0x49,
-     0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x01, 0x12, 0x29,
-     0x0a, 0x25, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c,
-     0x45, 0x44, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45,
-     0x4e, 0x54, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x49,
-     0x4d, 0x50, 0x4c, 0x10, 0x09, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x54, 0x45,
+     0x18, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45,
+     0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e,
+     0x45, 0x10, 0x01, 0x12, 0x2d, 0x0a, 0x29, 0x43, 0x43, 0x5f, 0x53, 0x43,
+     0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49,
+     0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x42, 0x45, 0x47, 0x49,
+     0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
+     0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48,
+     0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f,
+     0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x03, 0x12, 0x2a,
+     0x0a, 0x26, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c,
+     0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x43,
+     0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f,
+     0x54, 0x52, 0x45, 0x45, 0x10, 0x04, 0x12, 0x28, 0x0a, 0x24, 0x43, 0x43,
+     0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x49,
+     0x46, 0x5f, 0x50, 0x4f, 0x53, 0x53, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x05,
+     0x12, 0x23, 0x0a, 0x1f, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44,
+     0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
+     0x44, 0x52, 0x41, 0x57, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x10,
+     0x06, 0x12, 0x22, 0x0a, 0x1e, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45,
+     0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e,
+     0x5f, 0x44, 0x52, 0x41, 0x57, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10,
+     0x07, 0x12, 0x3c, 0x0a, 0x38, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45,
+     0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e,
+     0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4c, 0x41, 0x59, 0x45, 0x52,
+     0x5f, 0x54, 0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f,
+     0x53, 0x49, 0x4e, 0x4b, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4f,
+     0x4e, 0x10, 0x08, 0x12, 0x25, 0x0a, 0x21, 0x43, 0x43, 0x5f, 0x53, 0x43,
+     0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49,
+     0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x5f, 0x54,
+     0x49, 0x4c, 0x45, 0x53, 0x10, 0x09, 0x12, 0x38, 0x0a, 0x34, 0x43, 0x43,
+     0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41,
+     0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
+     0x44, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x54,
+     0x52, 0x45, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x53, 0x49,
+     0x4e, 0x4b, 0x10, 0x0a, 0x12, 0x36, 0x0a, 0x32, 0x43, 0x43, 0x5f, 0x53,
+     0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54,
+     0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x45, 0x52, 0x46, 0x4f, 0x52, 0x4d, 0x5f,
+     0x49, 0x4d, 0x50, 0x4c, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x49, 0x4e,
+     0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x0b,
+     0x12, 0x42, 0x0a, 0x3e, 0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44,
+     0x55, 0x4c, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
+     0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e,
+     0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f,
+     0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44,
+     0x5f, 0x55, 0x4e, 0x54, 0x49, 0x4c, 0x10, 0x0c, 0x12, 0x41, 0x0a, 0x3d,
+     0x43, 0x43, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x52,
+     0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x54, 0x49,
+     0x46, 0x59, 0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x4d, 0x41, 0x49,
+     0x4e, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f,
+     0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x4f, 0x4f,
+     0x4e, 0x10, 0x0d, 0x0a, 0xe0, 0x04, 0x0a, 0x3d, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f,
+     0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72,
+     0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x22, 0x8d, 0x04, 0x0a, 0x13, 0x43, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
+     0x65, 0x72, 0x12, 0x40, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52,
+     0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x06,
+     0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
+     0x32, 0x34, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
+     0x65, 0x72, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x72, 0x6f, 0x70,
+     0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73,
+     0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
+     0x52, 0x0b, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63,
+     0x65, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73,
+     0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
+     0x04, 0x52, 0x0d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x71, 0x75,
+     0x65, 0x6e, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x66, 0x66, 0x65,
+     0x63, 0x74, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x6e, 0x65,
+     0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x61, 0x66,
+     0x66, 0x65, 0x63, 0x74, 0x73, 0x53, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x6e,
+     0x65, 0x73, 0x73, 0x22, 0x6d, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65,
+     0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x4e, 0x4f,
+     0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x53, 0x49,
+     0x52, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41,
+     0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x54, 0x45, 0x44,
+     0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54,
+     0x41, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x54, 0x45,
+     0x44, 0x5f, 0x50, 0x41, 0x52, 0x54, 0x49, 0x41, 0x4c, 0x10, 0x02, 0x12,
+     0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x52, 0x4f,
+     0x50, 0x50, 0x45, 0x44, 0x10, 0x03, 0x22, 0x7e, 0x0a, 0x0f, 0x46, 0x72,
+     0x61, 0x6d, 0x65, 0x44, 0x72, 0x6f, 0x70, 0x52, 0x65, 0x61, 0x73, 0x6f,
+     0x6e, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f,
+     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+     0x00, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f,
+     0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x5f, 0x43, 0x4f, 0x4d, 0x50,
+     0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12,
+     0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f,
+     0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18,
+     0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e,
+     0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52,
+     0x10, 0x03, 0x0a, 0x86, 0x02, 0x0a, 0x3f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
+     0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x73, 0x61,
+     0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x22, 0x35, 0x0a, 0x0d, 0x48, 0x69, 0x73, 0x74, 0x6f,
+     0x67, 0x72, 0x61, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
+     0x69, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x69,
+     0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
+     0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x7b,
+     0x0a, 0x15, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x48, 0x69, 0x73, 0x74,
+     0x6f, 0x67, 0x72, 0x61, 0x6d, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12,
+     0x1b, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65,
+     0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+     0x12, 0x16, 0x0a, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x03,
+     0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+     0x12, 0x19, 0x0a, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x69, 0x64,
+     0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6e, 0x61, 0x6d, 0x65,
+     0x49, 0x69, 0x64, 0x0a, 0x79, 0x0a, 0x3c, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
+     0x6b, 0x65, 0x79, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
+     0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x22, 0x28, 0x0a, 0x12, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4b, 0x65,
+     0x79, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12,
+     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+     0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x0a, 0x89, 0x0d, 0x0a, 0x3b, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61,
+     0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f,
+     0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x22, 0xb8, 0x0c, 0x0a, 0x11, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66,
+     0x6f, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69,
+     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x72, 0x61,
+     0x63, 0x65, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e,
+     0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52,
+     0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x12, 0x66, 0x72, 0x61,
+     0x6d, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65,
+     0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x64, 0x65,
+     0x49, 0x64, 0x12, 0x57, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e,
+     0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x03,
+     0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e,
+     0x66, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74,
+     0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e,
+     0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x69,
+     0x73, 0x5f, 0x63, 0x6f, 0x61, 0x6c, 0x65, 0x73, 0x63, 0x65, 0x64, 0x18,
+     0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x43, 0x6f, 0x61,
+     0x6c, 0x65, 0x73, 0x63, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x65,
+     0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c,
+     0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x67,
+     0x65, 0x73, 0x74, 0x75, 0x72, 0x65, 0x53, 0x63, 0x72, 0x6f, 0x6c, 0x6c,
+     0x49, 0x64, 0x1a, 0x88, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x70, 0x6f,
+     0x6e, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x5e, 0x0a, 0x0e,
+     0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79,
+     0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x37, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74,
+     0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x61, 0x74,
+     0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e,
+     0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f,
+     0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07,
+     0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x04, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x65, 0x55, 0x73, 0x22, 0xf2, 0x02,
+     0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54,
+     0x45, 0x50, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+     0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x54, 0x45, 0x50,
+     0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f,
+     0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x49, 0x10, 0x03, 0x12, 0x20,
+     0x0a, 0x1c, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c,
+     0x45, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+     0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x10, 0x05, 0x12, 0x28, 0x0a, 0x24,
+     0x53, 0x54, 0x45, 0x50, 0x5f, 0x44, 0x49, 0x44, 0x5f, 0x48, 0x41, 0x4e,
+     0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x41, 0x4e,
+     0x44, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c,
+     0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x48,
+     0x41, 0x4e, 0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f,
+     0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x10, 0x04,
+     0x12, 0x22, 0x0a, 0x1e, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x4d, 0x41, 0x49,
+     0x4e, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x53, 0x43, 0x52,
+     0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02,
+     0x12, 0x27, 0x0a, 0x23, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x48, 0x41, 0x4e,
+     0x44, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56,
+     0x45, 0x4e, 0x54, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x43, 0x4f, 0x4d,
+     0x4d, 0x49, 0x54, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x54, 0x45,
      0x50, 0x5f, 0x48, 0x41, 0x4e, 0x44, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e,
-     0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4d,
-     0x50, 0x4c, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x45, 0x50,
-     0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52,
-     0x53, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x45, 0x50, 0x5f,
-     0x44, 0x52, 0x41, 0x57, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x53, 0x57, 0x41,
-     0x50, 0x10, 0x07, 0x22, 0xf5, 0x05, 0x0a, 0x14, 0x4c, 0x61, 0x74, 0x65,
-     0x6e, 0x63, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74,
-     0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4d, 0x50,
-     0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
-     0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2b, 0x0a, 0x27, 0x43,
-     0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50,
-     0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54,
-     0x45, 0x4e, 0x43, 0x59, 0x5f, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x52,
-     0x57, 0x48, 0x10, 0x01, 0x12, 0x38, 0x0a, 0x34, 0x43, 0x4f, 0x4d, 0x50,
-     0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f,
-     0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43,
-     0x59, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44,
-     0x41, 0x54, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x4c,
-     0x10, 0x02, 0x12, 0x3e, 0x0a, 0x3a, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e,
+     0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4d, 0x41,
+     0x49, 0x4e, 0x5f, 0x4f, 0x52, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x10, 0x09,
+     0x12, 0x21, 0x0a, 0x1d, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x48, 0x41, 0x4e,
+     0x44, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45,
+     0x56, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x10, 0x0a, 0x12,
+     0x15, 0x0a, 0x11, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x53, 0x57, 0x41, 0x50,
+     0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x53, 0x10, 0x06, 0x12, 0x16,
+     0x0a, 0x12, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x5f,
+     0x41, 0x4e, 0x44, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x10, 0x07, 0x22, 0xf5,
+     0x05, 0x0a, 0x14, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6f,
+     0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
+     0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54,
+     0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+     0x10, 0x00, 0x12, 0x2b, 0x0a, 0x27, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e,
      0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56,
      0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f,
-     0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c,
-     0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47,
-     0x49, 0x4e, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x2a, 0x0a, 0x26, 0x43, 0x4f,
-     0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55,
-     0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45,
-     0x4e, 0x43, 0x59, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x4c,
-     0x10, 0x04, 0x12, 0x24, 0x0a, 0x20, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e,
-     0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56,
-     0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f,
-     0x55, 0x49, 0x10, 0x05, 0x12, 0x2f, 0x0a, 0x2b, 0x43, 0x4f, 0x4d, 0x50,
-     0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f,
-     0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43,
-     0x59, 0x5f, 0x52, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x45, 0x52, 0x5f, 0x4d,
-     0x41, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d,
-     0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54,
-     0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e,
-     0x43, 0x59, 0x5f, 0x52, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47,
-     0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x44, 0x5f, 0x4d,
-     0x41, 0x49, 0x4e, 0x10, 0x07, 0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d,
-     0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54,
-     0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e,
-     0x43, 0x59, 0x5f, 0x52, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47,
-     0x5f, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x44, 0x5f, 0x49,
-     0x4d, 0x50, 0x4c, 0x10, 0x08, 0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d,
-     0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54,
-     0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e,
-     0x43, 0x59, 0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50,
-     0x44, 0x41, 0x54, 0x45, 0x5f, 0x4c, 0x41, 0x53, 0x54, 0x5f, 0x45, 0x56,
-     0x45, 0x4e, 0x54, 0x10, 0x09, 0x12, 0x29, 0x0a, 0x25, 0x43, 0x4f, 0x4d,
-     0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54,
-     0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e,
-     0x43, 0x59, 0x5f, 0x41, 0x43, 0x4b, 0x5f, 0x52, 0x57, 0x48, 0x10, 0x0a,
-     0x12, 0x2f, 0x0a, 0x2b, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e,
+     0x42, 0x45, 0x47, 0x49, 0x4e, 0x5f, 0x52, 0x57, 0x48, 0x10, 0x01, 0x12,
+     0x38, 0x0a, 0x34, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54,
+     0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54,
+     0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x53, 0x43, 0x52,
+     0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4f,
+     0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x3e, 0x0a,
+     0x3a, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49,
+     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c,
+     0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x46, 0x49, 0x52, 0x53, 0x54,
+     0x5f, 0x53, 0x43, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44, 0x41,
+     0x54, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x4c, 0x10,
+     0x03, 0x12, 0x2a, 0x0a, 0x26, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45,
+     0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45,
+     0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x4f,
+     0x52, 0x49, 0x47, 0x49, 0x4e, 0x41, 0x4c, 0x10, 0x04, 0x12, 0x24, 0x0a,
+     0x20, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49,
+     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c,
+     0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x55, 0x49, 0x10, 0x05, 0x12,
+     0x2f, 0x0a, 0x2b, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54,
+     0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54,
+     0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x52, 0x45, 0x4e,
+     0x44, 0x45, 0x52, 0x45, 0x52, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x10, 0x06,
+     0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e,
      0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e,
      0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x52, 0x45,
-     0x4e, 0x44, 0x45, 0x52, 0x45, 0x52, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x10,
-     0x0b, 0x12, 0x2f, 0x0a, 0x2b, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45,
-     0x4e, 0x54, 0x5f, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x5f, 0x43,
-     0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45,
-     0x43, 0x45, 0x49, 0x56, 0x45, 0x44, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
-     0x10, 0x0c, 0x12, 0x29, 0x0a, 0x25, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e,
-     0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56,
-     0x45, 0x4e, 0x54, 0x5f, 0x47, 0x50, 0x55, 0x5f, 0x53, 0x57, 0x41, 0x50,
-     0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52, 0x10, 0x0d, 0x12, 0x2c, 0x0a,
-     0x28, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49,
-     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c,
-     0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45,
-     0x5f, 0x53, 0x57, 0x41, 0x50, 0x10, 0x0e, 0x0a, 0xb8, 0x08, 0x0a, 0x39,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f,
-     0x69, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
+     0x4e, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x43, 0x48, 0x45,
+     0x44, 0x55, 0x4c, 0x45, 0x44, 0x5f, 0x4d, 0x41, 0x49, 0x4e, 0x10, 0x07,
+     0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e,
+     0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+     0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x52, 0x45,
+     0x4e, 0x44, 0x45, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x43, 0x48, 0x45,
+     0x44, 0x55, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x10, 0x08,
+     0x12, 0x3a, 0x0a, 0x36, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e,
+     0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+     0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x53, 0x43,
+     0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f,
+     0x4c, 0x41, 0x53, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x10, 0x09,
+     0x12, 0x29, 0x0a, 0x25, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e,
+     0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e,
+     0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x41, 0x43,
+     0x4b, 0x5f, 0x52, 0x57, 0x48, 0x10, 0x0a, 0x12, 0x2f, 0x0a, 0x2b, 0x43,
+     0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50,
+     0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54,
+     0x45, 0x4e, 0x43, 0x59, 0x5f, 0x52, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x45,
+     0x52, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x10, 0x0b, 0x12, 0x2f, 0x0a, 0x2b,
+     0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x49,
+     0x53, 0x50, 0x4c, 0x41, 0x59, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53,
+     0x49, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45,
+     0x44, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x0c, 0x12, 0x29, 0x0a,
+     0x25, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49,
+     0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x47,
+     0x50, 0x55, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x42, 0x55, 0x46, 0x46,
+     0x45, 0x52, 0x10, 0x0d, 0x12, 0x2c, 0x0a, 0x28, 0x43, 0x4f, 0x4d, 0x50,
+     0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f,
+     0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x41, 0x54, 0x45, 0x4e, 0x43,
+     0x59, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x5f, 0x53, 0x57, 0x41, 0x50,
+     0x10, 0x0e, 0x0a, 0xb8, 0x08, 0x0a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
+     0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x69, 0x70, 0x63, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xe9, 0x07,
+     0x0a, 0x0f, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x65, 0x67, 0x61,
+     0x63, 0x79, 0x49, 0x70, 0x63, 0x12, 0x52, 0x0a, 0x0d, 0x6d, 0x65, 0x73,
+     0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x01,
+     0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
+     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49,
+     0x70, 0x63, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6c,
+     0x61, 0x73, 0x73, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x73,
+     0x73, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x4c, 0x69, 0x6e, 0x65, 0x22, 0xde, 0x06, 0x0a, 0x0c, 0x4d, 0x65, 0x73,
+     0x73, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x15, 0x0a,
+     0x11, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+     0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10,
+     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41, 0x55, 0x54, 0x4f, 0x4d, 0x41,
+     0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c,
+     0x41, 0x53, 0x53, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x02, 0x12,
+     0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x50, 0x41, 0x47,
+     0x45, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53,
+     0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x43,
+     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x57, 0x49, 0x44, 0x47, 0x45, 0x54, 0x10,
+     0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x49,
+     0x4e, 0x50, 0x55, 0x54, 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c,
+     0x41, 0x53, 0x53, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10, 0x07, 0x12, 0x10,
+     0x0a, 0x0c, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x57, 0x4f, 0x52, 0x4b,
+     0x45, 0x52, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53,
+     0x53, 0x5f, 0x4e, 0x41, 0x43, 0x4c, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11,
+     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x47, 0x50, 0x55, 0x5f, 0x43, 0x48,
+     0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x0a, 0x12, 0x0f, 0x0a, 0x0b, 0x43,
+     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x10, 0x0b,
+     0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x50, 0x50,
+     0x41, 0x50, 0x49, 0x10, 0x0c, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4c, 0x41,
+     0x53, 0x53, 0x5f, 0x43, 0x48, 0x52, 0x4f, 0x4d, 0x45, 0x10, 0x0d, 0x12,
+     0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x44, 0x52, 0x41,
+     0x47, 0x10, 0x0e, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41, 0x53, 0x53,
+     0x5f, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x10, 0x0f, 0x12, 0x13, 0x0a, 0x0f,
+     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53,
+     0x49, 0x4f, 0x4e, 0x10, 0x10, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4c, 0x41,
+     0x53, 0x53, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x49, 0x4e, 0x50, 0x55,
+     0x54, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x11, 0x12, 0x14,
+     0x0a, 0x10, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x42, 0x4c, 0x49, 0x4e,
+     0x4b, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10, 0x12, 0x12, 0x17, 0x0a, 0x13,
+     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53,
+     0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x13, 0x12, 0x13, 0x0a,
+     0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x45, 0x52, 0x45,
+     0x4e, 0x44, 0x45, 0x52, 0x10, 0x14, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c,
+     0x41, 0x53, 0x53, 0x5f, 0x43, 0x48, 0x52, 0x4f, 0x4d, 0x4f, 0x54, 0x49,
+     0x4e, 0x47, 0x10, 0x15, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4c, 0x41, 0x53,
+     0x53, 0x5f, 0x42, 0x52, 0x4f, 0x57, 0x53, 0x45, 0x52, 0x5f, 0x50, 0x4c,
+     0x55, 0x47, 0x49, 0x4e, 0x10, 0x16, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4c,
+     0x41, 0x53, 0x53, 0x5f, 0x41, 0x4e, 0x44, 0x52, 0x4f, 0x49, 0x44, 0x5f,
+     0x57, 0x45, 0x42, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x17, 0x12, 0x13,
+     0x0a, 0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4e, 0x41, 0x43, 0x4c,
+     0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x18, 0x12, 0x19, 0x0a, 0x15, 0x43,
+     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54,
+     0x45, 0x44, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x10, 0x19, 0x12, 0x0e,
+     0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x43, 0x41, 0x53, 0x54,
+     0x10, 0x1a, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f,
+     0x47, 0x49, 0x4e, 0x5f, 0x4a, 0x41, 0x56, 0x41, 0x5f, 0x42, 0x52, 0x49,
+     0x44, 0x47, 0x45, 0x10, 0x1b, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4c, 0x41,
+     0x53, 0x53, 0x5f, 0x43, 0x48, 0x52, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x54,
+     0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x49,
+     0x4e, 0x47, 0x10, 0x1c, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4c, 0x41, 0x53,
+     0x53, 0x5f, 0x4f, 0x5a, 0x4f, 0x4e, 0x45, 0x5f, 0x47, 0x50, 0x55, 0x10,
+     0x1d, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x57,
+     0x45, 0x42, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10, 0x1e, 0x12, 0x17, 0x0a,
+     0x13, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4e, 0x45, 0x54, 0x57, 0x4f,
+     0x52, 0x4b, 0x5f, 0x48, 0x49, 0x4e, 0x54, 0x53, 0x10, 0x1f, 0x12, 0x1f,
+     0x0a, 0x1b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45, 0x58, 0x54, 0x45,
+     0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x47, 0x55, 0x45, 0x53, 0x54,
+     0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x20, 0x12, 0x14, 0x0a, 0x10, 0x43,
+     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x47, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x56,
+     0x49, 0x45, 0x57, 0x10, 0x21, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4c, 0x41,
+     0x53, 0x53, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x5f, 0x50, 0x4c, 0x41,
+     0x59, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, 0x45,
+     0x10, 0x22, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f,
+     0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x57, 0x4f,
+     0x52, 0x4b, 0x45, 0x52, 0x10, 0x23, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4c,
+     0x41, 0x53, 0x53, 0x5f, 0x53, 0x55, 0x42, 0x52, 0x45, 0x53, 0x4f, 0x55,
+     0x52, 0x43, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x24,
+     0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x55, 0x4e,
+     0x46, 0x52, 0x45, 0x45, 0x5a, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x52,
+     0x41, 0x4d, 0x45, 0x10, 0x25, 0x0a, 0x98, 0x01, 0x0a, 0x3b, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x70,
+     0x75, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
      0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x22, 0xe9, 0x07, 0x0a, 0x0f, 0x43, 0x68, 0x72, 0x6f, 0x6d,
-     0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x70, 0x63, 0x12, 0x52,
-     0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6c,
-     0x61, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x65,
-     0x67, 0x61, 0x63, 0x79, 0x49, 0x70, 0x63, 0x2e, 0x4d, 0x65, 0x73, 0x73,
-     0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x0c, 0x6d, 0x65,
-     0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x21,
-     0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69,
-     0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x65,
-     0x73, 0x73, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xde, 0x06,
-     0x0a, 0x0c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61,
-     0x73, 0x73, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f,
-     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
-     0x00, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41,
-     0x55, 0x54, 0x4f, 0x4d, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12,
-     0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x46, 0x52, 0x41,
-     0x4d, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53,
-     0x53, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a,
-     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x04,
-     0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x57, 0x49,
-     0x44, 0x47, 0x45, 0x54, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c,
-     0x41, 0x53, 0x53, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x10, 0x06, 0x12,
-     0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x54, 0x45, 0x53,
-     0x54, 0x10, 0x07, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4c, 0x41, 0x53, 0x53,
-     0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x45, 0x52, 0x10, 0x08, 0x12, 0x0e, 0x0a,
-     0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4e, 0x41, 0x43, 0x4c, 0x10,
-     0x09, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x47,
-     0x50, 0x55, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x0a,
-     0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4d, 0x45,
-     0x44, 0x49, 0x41, 0x10, 0x0b, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x41,
-     0x53, 0x53, 0x5f, 0x50, 0x50, 0x41, 0x50, 0x49, 0x10, 0x0c, 0x12, 0x10,
-     0x0a, 0x0c, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x43, 0x48, 0x52, 0x4f,
-     0x4d, 0x45, 0x10, 0x0d, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53,
-     0x53, 0x5f, 0x44, 0x52, 0x41, 0x47, 0x10, 0x0e, 0x12, 0x0f, 0x0a, 0x0b,
-     0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x49, 0x4e, 0x54, 0x10,
-     0x0f, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45,
-     0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x10, 0x12, 0x1b,
-     0x0a, 0x17, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x54, 0x45, 0x58, 0x54,
-     0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e,
-     0x54, 0x10, 0x11, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x41, 0x53, 0x53,
-     0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10,
-     0x12, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41,
-     0x43, 0x43, 0x45, 0x53, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59,
-     0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f,
-     0x50, 0x52, 0x45, 0x52, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x10, 0x14, 0x12,
-     0x14, 0x0a, 0x10, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x43, 0x48, 0x52,
-     0x4f, 0x4d, 0x4f, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x15, 0x12, 0x18, 0x0a,
-     0x14, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x42, 0x52, 0x4f, 0x57, 0x53,
-     0x45, 0x52, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e, 0x10, 0x16, 0x12,
-     0x1a, 0x0a, 0x16, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x41, 0x4e, 0x44,
-     0x52, 0x4f, 0x49, 0x44, 0x5f, 0x57, 0x45, 0x42, 0x5f, 0x56, 0x49, 0x45,
-     0x57, 0x10, 0x17, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53,
-     0x5f, 0x4e, 0x41, 0x43, 0x4c, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x18,
-     0x12, 0x19, 0x0a, 0x15, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45, 0x4e,
-     0x43, 0x52, 0x59, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x4d, 0x45, 0x44, 0x49,
-     0x41, 0x10, 0x19, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4c, 0x41, 0x53, 0x53,
-     0x5f, 0x43, 0x41, 0x53, 0x54, 0x10, 0x1a, 0x12, 0x19, 0x0a, 0x15, 0x43,
-     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x47, 0x49, 0x4e, 0x5f, 0x4a, 0x41, 0x56,
-     0x41, 0x5f, 0x42, 0x52, 0x49, 0x44, 0x47, 0x45, 0x10, 0x1b, 0x12, 0x21,
-     0x0a, 0x1d, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x43, 0x48, 0x52, 0x4f,
-     0x4d, 0x45, 0x5f, 0x55, 0x54, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x50,
-     0x52, 0x49, 0x4e, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x1c, 0x12, 0x13, 0x0a,
-     0x0f, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4f, 0x5a, 0x4f, 0x4e, 0x45,
-     0x5f, 0x47, 0x50, 0x55, 0x10, 0x1d, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c,
-     0x41, 0x53, 0x53, 0x5f, 0x57, 0x45, 0x42, 0x5f, 0x54, 0x45, 0x53, 0x54,
-     0x10, 0x1e, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f,
-     0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x5f, 0x48, 0x49, 0x4e, 0x54,
-     0x53, 0x10, 0x1f, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4c, 0x41, 0x53, 0x53,
-     0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e, 0x53, 0x5f,
-     0x47, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x20,
-     0x12, 0x14, 0x0a, 0x10, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x47, 0x55,
-     0x45, 0x53, 0x54, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x10, 0x21, 0x12, 0x1f,
-     0x0a, 0x1b, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x4d, 0x45, 0x44, 0x49,
-     0x41, 0x5f, 0x50, 0x4c, 0x41, 0x59, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x4c,
-     0x45, 0x47, 0x41, 0x54, 0x45, 0x10, 0x22, 0x12, 0x1a, 0x0a, 0x16, 0x43,
-     0x4c, 0x41, 0x53, 0x53, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49,
-     0x4f, 0x4e, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x45, 0x52, 0x10, 0x23, 0x12,
-     0x1c, 0x0a, 0x18, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x5f, 0x53, 0x55, 0x42,
-     0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x46, 0x49, 0x4c,
-     0x54, 0x45, 0x52, 0x10, 0x24, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4c, 0x41,
-     0x53, 0x53, 0x5f, 0x55, 0x4e, 0x46, 0x52, 0x45, 0x45, 0x5a, 0x41, 0x42,
-     0x4c, 0x45, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x25, 0x0a, 0x98,
+     0x6f, 0x73, 0x22, 0x48, 0x0a, 0x11, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x75, 0x6d, 0x70, 0x12,
+     0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73,
+     0x61, 0x67, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x71, 0x75, 0x65, 0x75,
+     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x73, 0x65, 0x6e,
+     0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x49, 0x6e, 0x51,
+     0x75, 0x65, 0x75, 0x65, 0x0a, 0xa9, 0x01, 0x0a, 0x3e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x5f, 0x6d, 0x6f, 0x6a, 0x6f, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
+     0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x22, 0x56, 0x0a, 0x13, 0x43, 0x68, 0x72, 0x6f,
+     0x6d, 0x65, 0x4d, 0x6f, 0x6a, 0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49,
+     0x6e, 0x66, 0x6f, 0x12, 0x3f, 0x0a, 0x1c, 0x77, 0x61, 0x74, 0x63, 0x68,
+     0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x69, 0x6e,
+     0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x77, 0x61, 0x74, 0x63, 0x68,
+     0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65,
+     0x72, 0x66, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x0a, 0xb7, 0x02, 0x0a,
+     0x47, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74,
+     0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72,
+     0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72,
+     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x5c, 0x0a, 0x1c, 0x43, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x72, 0x53,
+     0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
+     0x65, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x61, 0x69, 0x6c, 0x5f, 0x6d, 0x6f,
+     0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x52, 0x41, 0x49,
+     0x4c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x72, 0x61, 0x69, 0x6c, 0x4d,
+     0x6f, 0x64, 0x65, 0x2a, 0x7d, 0x0a, 0x0e, 0x43, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x52, 0x41, 0x49, 0x4c, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a,
+     0x0e, 0x52, 0x41, 0x49, 0x4c, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x4e,
+     0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x41, 0x49,
+     0x4c, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f,
+     0x4e, 0x53, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x41, 0x49,
+     0x4c, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x41, 0x4e, 0x49, 0x4d, 0x41,
+     0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x41,
+     0x49, 0x4c, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x49, 0x44, 0x4c, 0x45,
+     0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x41, 0x49, 0x4c, 0x5f, 0x4d,
+     0x4f, 0x44, 0x45, 0x5f, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x0a, 0x98,
      0x01, 0x0a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
      0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65,
      0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
@@ -1310,190 +1414,298 @@
      0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
      0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68,
      0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61,
-     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x0a, 0x96, 0x1c,
-     0x0a, 0x33, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
+     0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x0a, 0xaa, 0x01,
+     0x0a, 0x47, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
      0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f,
      0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f,
-     0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x38,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x64, 0x65,
-     0x62, 0x75, 0x67, 0x5f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69,
-     0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x33, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x6c, 0x6f, 0x67, 0x5f,
-     0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
-     0x2f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74,
-     0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x49, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61,
-     0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74,
-     0x6f, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72,
-     0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x3d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f,
-     0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f,
-     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x1a, 0x3f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
+     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f,
+     0x77, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65,
+     0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x4e, 0x0a, 0x1b, 0x43, 0x68,
+     0x72, 0x6f, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x48, 0x61,
+     0x6e, 0x64, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
+     0x6f, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x70, 0x69, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x03, 0x64, 0x70, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x6d,
+     0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
+     0x01, 0x28, 0x0d, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x49, 0x64, 0x0a, 0x92, 0x25, 0x0a, 0x33, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x1a, 0x38, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
      0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61,
      0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65,
-     0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x68, 0x69,
-     0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x73, 0x61, 0x6d, 0x70,
-     0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3c, 0x70, 0x72,
+     0x6e, 0x74, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x61, 0x6e, 0x6e,
+     0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x1a, 0x33, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
+     0x2f, 0x6c, 0x6f, 0x67, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f,
+     0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x65,
+     0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x1a, 0x45, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x61, 0x70, 0x70,
+     0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61,
+     0x74, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x1a, 0x49, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
+     0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70,
+     0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64,
+     0x75, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72,
+     0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76,
+     0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65,
+     0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3f, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f,
+     0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x1a, 0x3c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f,
+     0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f,
+     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x65, 0x64,
+     0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x1a, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x61, 0x74,
+     0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61,
+     0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65,
+     0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x65,
+     0x67, 0x61, 0x63, 0x79, 0x5f, 0x69, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x1a, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6d, 0x65, 0x73,
+     0x73, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6d, 0x70, 0x2e, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x1a, 0x3e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61,
+     0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65,
+     0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6d, 0x6f,
+     0x6a, 0x6f, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x47, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x5f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x72, 0x5f, 0x73,
+     0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61,
+     0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x39, 0x70, 0x72,
      0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
      0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
      0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72,
-     0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3b,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79,
-     0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-     0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74,
-     0x72, 0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63,
-     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79,
-     0x5f, 0x69, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x39,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x76,
-     0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x90, 0x15,
-     0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74,
-     0x12, 0x23, 0x0a, 0x0d, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
-     0x5f, 0x69, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52,
-     0x0c, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x69, 0x64,
-     0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
-     0x69, 0x65, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63,
-     0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1b, 0x0a,
-     0x08, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x69, 0x64, 0x18, 0x0a, 0x20,
-     0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x07, 0x6e, 0x61, 0x6d, 0x65, 0x49,
-     0x69, 0x64, 0x12, 0x14, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x17,
-     0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-     0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01,
-     0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
-     0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65,
-     0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72,
-     0x61, 0x63, 0x6b, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x09, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69,
-     0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
-     0x75, 0x65, 0x12, 0x39, 0x0a, 0x19, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x74, 0x72, 0x61, 0x63,
-     0x6b, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28,
-     0x04, 0x52, 0x16, 0x65, 0x78, 0x74, 0x72, 0x61, 0x43, 0x6f, 0x75, 0x6e,
-     0x74, 0x65, 0x72, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64,
-     0x73, 0x12, 0x30, 0x0a, 0x14, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
-     0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x03, 0x52, 0x12, 0x65, 0x78, 0x74,
-     0x72, 0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
-     0x75, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x11, 0x64, 0x65, 0x62, 0x75, 0x67,
-     0x5f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-     0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
-     0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x64, 0x65, 0x62, 0x75, 0x67, 0x41,
-     0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x45,
-     0x0a, 0x0e, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75,
-     0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
+     0x6d, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x76, 0x65, 0x6e,
+     0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x47, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x68, 0x61, 0x6e,
+     0x64, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e,
+     0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x37, 0x70, 0x72,
+     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x63,
+     0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x73, 0x6f, 0x75, 0x72,
+     0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x1a, 0x0a, 0x0a, 0x54, 0x72,
+     0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d,
+     0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x69, 0x64,
+     0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x61, 0x74,
+     0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x69, 0x64, 0x73, 0x12, 0x1e, 0x0a,
+     0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18,
+     0x16, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67,
+     0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x08, 0x6e, 0x61, 0x6d,
+     0x65, 0x5f, 0x69, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x48,
+     0x00, 0x52, 0x07, 0x6e, 0x61, 0x6d, 0x65, 0x49, 0x69, 0x64, 0x12, 0x14,
+     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09,
+     0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x04,
+     0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20,
      0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x78, 0x65,
-     0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x61, 0x73, 0x6b,
-     0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a,
-     0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-     0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x4c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
-     0x0a, 0x6c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
-     0x5d, 0x0a, 0x12, 0x63, 0x63, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75,
-     0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x18, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74,
-     0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53,
-     0x74, 0x61, 0x74, 0x65, 0x52, 0x10, 0x63, 0x63, 0x53, 0x63, 0x68, 0x65,
-     0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c,
-     0x0a, 0x11, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x75, 0x73, 0x65,
-     0x72, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52,
-     0x0f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x45,
-     0x76, 0x65, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x14, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72,
-     0x76, 0x69, 0x63, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4b,
-     0x65, 0x79, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
-     0x12, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4b, 0x65, 0x79, 0x65, 0x64,
-     0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x11, 0x63,
-     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79,
-     0x5f, 0x69, 0x70, 0x63, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c,
-     0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x70, 0x63, 0x52, 0x0f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x70,
-     0x63, 0x12, 0x5e, 0x0a, 0x17, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
-     0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x73, 0x61,
-     0x6d, 0x70, 0x6c, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x48,
+     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x76,
+     0x65, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79,
+     0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f,
+     0x75, 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09,
+     0x74, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x12, 0x23, 0x0a,
+     0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c,
+     0x75, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f,
+     0x75, 0x6e, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39,
+     0x0a, 0x19, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+     0x74, 0x65, 0x72, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x75,
+     0x69, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x04, 0x52, 0x16, 0x65,
+     0x78, 0x74, 0x72, 0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x54,
+     0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x73, 0x12, 0x30, 0x0a,
+     0x14, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+     0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x0c, 0x20,
+     0x03, 0x28, 0x03, 0x52, 0x12, 0x65, 0x78, 0x74, 0x72, 0x61, 0x43, 0x6f,
+     0x75, 0x6e, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12,
+     0x19, 0x0a, 0x08, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x73, 0x18,
+     0x24, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x49,
+     0x64, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e,
+     0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69,
+     0x64, 0x73, 0x18, 0x2a, 0x20, 0x03, 0x28, 0x04, 0x52, 0x12, 0x74, 0x65,
+     0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x6c, 0x6f,
+     0x77, 0x49, 0x64, 0x73, 0x12, 0x4d, 0x0a, 0x11, 0x64, 0x65, 0x62, 0x75,
+     0x67, 0x5f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+     0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x41, 0x6e, 0x6e, 0x6f, 0x74,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x64, 0x65, 0x62, 0x75, 0x67,
+     0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
+     0x45, 0x0a, 0x0e, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x65, 0x78, 0x65, 0x63,
+     0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x1e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x78,
+     0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x61, 0x73,
+     0x6b, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c,
+     0x0a, 0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+     0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+     0x12, 0x5d, 0x0a, 0x12, 0x63, 0x63, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64,
+     0x75, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x18,
+     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
+     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69,
+     0x74, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72,
+     0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x10, 0x63, 0x63, 0x53, 0x63, 0x68,
+     0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
+     0x4c, 0x0a, 0x11, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x75, 0x73,
+     0x65, 0x72, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x19, 0x20, 0x01,
+     0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74,
+     0x52, 0x0f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72,
+     0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x55, 0x0a, 0x14, 0x63, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x65, 0x64, 0x5f, 0x73, 0x65,
+     0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x4b, 0x65, 0x79, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+     0x52, 0x12, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4b, 0x65, 0x79, 0x65,
+     0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x11,
+     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63,
+     0x79, 0x5f, 0x69, 0x70, 0x63, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, 0x70, 0x63, 0x52, 0x0f, 0x63,
+     0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49,
+     0x70, 0x63, 0x12, 0x5e, 0x0a, 0x17, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x73,
+     0x61, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x26, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x53, 0x61, 0x6d,
+     0x70, 0x6c, 0x65, 0x52, 0x15, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x48,
      0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x53, 0x61, 0x6d, 0x70,
-     0x6c, 0x65, 0x52, 0x15, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x48, 0x69,
-     0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x53, 0x61, 0x6d, 0x70, 0x6c,
-     0x65, 0x12, 0x52, 0x0a, 0x13, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
-     0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
-     0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e,
-     0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x11, 0x63, 0x68, 0x72, 0x6f,
-     0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66,
-     0x6f, 0x12, 0x58, 0x0a, 0x15, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74,
-     0x65, 0x72, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70,
+     0x6c, 0x65, 0x12, 0x52, 0x0a, 0x13, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66,
+     0x6f, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65,
+     0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x11, 0x63, 0x68, 0x72,
+     0x6f, 0x6d, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e,
+     0x66, 0x6f, 0x12, 0x58, 0x0a, 0x15, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72,
+     0x74, 0x65, 0x72, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72,
+     0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x52,
+     0x13, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65,
+     0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x6e, 0x0a, 0x1d,
+     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69,
+     0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65,
+     0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x27, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x1a, 0x63, 0x68,
+     0x72, 0x6f, 0x6d, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,
+     0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f,
+     0x12, 0x74, 0x0a, 0x1f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x72,
+     0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65,
+     0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
+     0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72,
+     0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x53,
+     0x74, 0x61, 0x74, 0x65, 0x52, 0x1c, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65,
+     0x64, 0x75, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x72,
+     0x0a, 0x1f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e,
+     0x64, 0x6f, 0x77, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x65,
+     0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x29, 0x20,
+     0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68,
+     0x72, 0x6f, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x48, 0x61,
+     0x6e, 0x64, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
+     0x6f, 0x52, 0x1b, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x57, 0x69, 0x6e,
+     0x64, 0x6f, 0x77, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x76, 0x65,
+     0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x0f, 0x73, 0x6f,
+     0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
+     0x6e, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65,
+     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+     0x73, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61,
+     0x74, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72,
+     0x63, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30,
+     0x0a, 0x13, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x69, 0x64, 0x18, 0x22, 0x20,
+     0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63,
+     0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x69, 0x64,
+     0x12, 0x52, 0x0a, 0x13, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6d,
+     0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6d, 0x70, 0x18,
+     0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+     0x65, 0x50, 0x75, 0x6d, 0x70, 0x52, 0x11, 0x63, 0x68, 0x72, 0x6f, 0x6d,
+     0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x75, 0x6d, 0x70,
+     0x12, 0x59, 0x0a, 0x16, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x6d,
+     0x6f, 0x6a, 0x6f, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e,
+     0x66, 0x6f, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70,
      0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x52, 0x13,
-     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52,
-     0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x12, 0x74,
+     0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4d, 0x6f, 0x6a,
+     0x6f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x13,
+     0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x4d, 0x6f, 0x6a, 0x6f, 0x45, 0x76,
+     0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a, 0x12, 0x74,
      0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c,
      0x74, 0x61, 0x5f, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48,
-     0x01, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+     0x02, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
      0x44, 0x65, 0x6c, 0x74, 0x61, 0x55, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x74,
      0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x61, 0x62, 0x73,
      0x6f, 0x6c, 0x75, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x18, 0x10, 0x20, 0x01,
-     0x28, 0x03, 0x48, 0x01, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
+     0x28, 0x03, 0x48, 0x02, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
      0x61, 0x6d, 0x70, 0x41, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x55,
      0x73, 0x12, 0x31, 0x0a, 0x14, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
      0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x75,
-     0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x02, 0x52, 0x11, 0x74,
+     0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x03, 0x52, 0x11, 0x74,
      0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x65, 0x6c,
      0x74, 0x61, 0x55, 0x73, 0x12, 0x37, 0x0a, 0x17, 0x74, 0x68, 0x72, 0x65,
      0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x62, 0x73, 0x6f,
      0x6c, 0x75, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28,
-     0x03, 0x48, 0x02, 0x52, 0x14, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54,
+     0x03, 0x48, 0x03, 0x52, 0x14, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54,
      0x69, 0x6d, 0x65, 0x41, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x55,
      0x73, 0x12, 0x45, 0x0a, 0x1e, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
      0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
      0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18,
-     0x08, 0x20, 0x01, 0x28, 0x03, 0x48, 0x03, 0x52, 0x1b, 0x74, 0x68, 0x72,
+     0x08, 0x20, 0x01, 0x28, 0x03, 0x48, 0x04, 0x52, 0x1b, 0x74, 0x68, 0x72,
      0x65, 0x61, 0x64, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69,
      0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x74, 0x61,
      0x12, 0x4b, 0x0a, 0x21, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x69,
      0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63,
      0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74,
-     0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x03, 0x52, 0x1e, 0x74,
+     0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x04, 0x52, 0x1e, 0x74,
      0x68, 0x72, 0x65, 0x61, 0x64, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63,
      0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x41, 0x62, 0x73,
      0x6f, 0x6c, 0x75, 0x74, 0x65, 0x12, 0x4a, 0x0a, 0x0c, 0x6c, 0x65, 0x67,
@@ -1588,30 +1800,32 @@
      0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52,
      0x10, 0x04, 0x2a, 0x06, 0x08, 0xe8, 0x07, 0x10, 0xac, 0x4d, 0x2a, 0x06,
      0x08, 0xac, 0x4d, 0x10, 0x91, 0x4e, 0x42, 0x0c, 0x0a, 0x0a, 0x6e, 0x61,
-     0x6d, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0b, 0x0a, 0x09,
-     0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0d, 0x0a,
-     0x0b, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x42, 0x1a, 0x0a, 0x18, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x69,
-     0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x22, 0x6e, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63,
-     0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c,
-     0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f,
-     0x75, 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09,
-     0x74, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x12, 0x39, 0x0a,
-     0x19, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x75, 0x69,
-     0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x04, 0x52, 0x16, 0x65, 0x78,
-     0x74, 0x72, 0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x54, 0x72,
-     0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x73, 0x22, 0x35, 0x0a, 0x0d,
-     0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
-     0x79, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x04, 0x52, 0x03, 0x69, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x69, 0x64, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x69, 0x69, 0x64, 0x12, 0x12,
-     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65}};
+     0x6d, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x17, 0x0a, 0x15,
+     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74,
+     0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0b, 0x0a,
+     0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x0d,
+     0x0a, 0x0b, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d,
+     0x65, 0x42, 0x1a, 0x0a, 0x18, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
+     0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x6e, 0x0a, 0x12, 0x54, 0x72, 0x61,
+     0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75,
+     0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x6b,
+     0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52,
+     0x09, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x12, 0x39,
+     0x0a, 0x19, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+     0x74, 0x65, 0x72, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x75,
+     0x69, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x04, 0x52, 0x16, 0x65,
+     0x78, 0x74, 0x72, 0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x54,
+     0x72, 0x61, 0x63, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x73, 0x22, 0x35, 0x0a,
+     0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
+     0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x69, 0x64, 0x18, 0x01, 0x20,
+     0x01, 0x28, 0x04, 0x52, 0x03, 0x69, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04,
+     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+     0x6e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e,
+     0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x69, 0x64,
+     0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x69, 0x69, 0x64, 0x12,
+     0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65}};
 
 }  // namespace perfetto
 
diff --git a/src/trace_processor/importers/proto/track_event_module.cc b/src/trace_processor/importers/proto/track_event_module.cc
index a2fcf28..eb9d536 100644
--- a/src/trace_processor/importers/proto/track_event_module.cc
+++ b/src/trace_processor/importers/proto/track_event_module.cc
@@ -18,6 +18,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/timestamped_trace_piece.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
@@ -31,7 +32,9 @@
 using perfetto::protos::pbzero::TracePacket;
 
 TrackEventModule::TrackEventModule(TraceProcessorContext* context)
-    : tokenizer_(context), parser_(context) {
+    : track_event_tracker_(new TrackEventTracker(context)),
+      tokenizer_(context, track_event_tracker_.get()),
+      parser_(context, track_event_tracker_.get()) {
   RegisterForField(TracePacket::kTrackEventFieldNumber, context);
   RegisterForField(TracePacket::kTrackDescriptorFieldNumber, context);
   RegisterForField(TracePacket::kThreadDescriptorFieldNumber, context);
@@ -87,5 +90,9 @@
   }
 }
 
+void TrackEventModule::OnIncrementalStateCleared(uint32_t packet_sequence_id) {
+  track_event_tracker_->OnIncrementalStateCleared(packet_sequence_id);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_module.h b/src/trace_processor/importers/proto/track_event_module.h
index de31d47..0632747 100644
--- a/src/trace_processor/importers/proto/track_event_module.h
+++ b/src/trace_processor/importers/proto/track_event_module.h
@@ -39,11 +39,14 @@
       PacketSequenceState* state,
       uint32_t field_id) override;
 
+  void OnIncrementalStateCleared(uint32_t) override;
+
   void ParsePacket(const protos::pbzero::TracePacket::Decoder& decoder,
                    const TimestampedTracePiece& ttp,
                    uint32_t field_id) override;
 
  private:
+  std::unique_ptr<TrackEventTracker> track_event_tracker_;
   TrackEventTokenizer tokenizer_;
   TrackEventParser parser_;
 };
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 7ed4c63..019d878 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -24,16 +24,19 @@
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/json/json_utils.h"
 #include "src/trace_processor/importers/proto/args_table_utils.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/track_event.descriptor.h"
+#include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/util/status_macros.h"
 
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
+#include "protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
@@ -116,15 +119,16 @@
                 TrackEventData* event_data,
                 ConstBytes blob)
       : context_(parser->context_),
+        track_event_tracker_(parser->track_event_tracker_),
         storage_(context_->storage.get()),
         parser_(parser),
         ts_(ts),
         event_data_(event_data),
-        sequence_state_(event_data_->sequence_state),
+        sequence_state_(event_data->sequence_state.get()),
         blob_(std::move(blob)),
         event_(blob_),
         legacy_event_(event_.legacy_event()),
-        defaults_(sequence_state_->GetTrackEventDefaults()) {}
+        defaults_(event_data->sequence_state->GetTrackEventDefaults()) {}
 
   util::Status Import() {
     // TODO(eseckler): This legacy event field will eventually be replaced by
@@ -155,15 +159,19 @@
 
     // TODO(eseckler): Replace phase with type and remove handling of
     // legacy_event_.phase() once it is no longer used by producers.
-    int32_t phase = ParsePhaseOrType();
+    char phase = static_cast<char>(ParsePhaseOrType());
 
-    switch (static_cast<char>(phase)) {
+    switch (phase) {
       case 'B':  // TRACE_EVENT_PHASE_BEGIN.
         return ParseThreadBeginEvent();
       case 'E':  // TRACE_EVENT_PHASE_END.
         return ParseThreadEndEvent();
       case 'X':  // TRACE_EVENT_PHASE_COMPLETE.
         return ParseThreadCompleteEvent();
+      case 's':  // TRACE_EVENT_PHASE_FLOW_BEGIN.
+      case 't':  // TRACE_EVENT_PHASE_FLOW_STEP.
+      case 'f':  // TRACE_EVENT_PHASE_FLOW_END.
+        return ParseFlowEventV1(phase);
       case 'i':
       case 'I':  // TRACE_EVENT_PHASE_INSTANT.
         return ParseThreadInstantEvent();
@@ -274,18 +282,19 @@
     // Determine track from track_uuid specified in either TrackEvent or
     // TrackEventDefaults. If a non-default track is not set, we either:
     //   a) fall back to the track specified by the sequence's (or event's) pid
-    //   +
-    //      tid (only in case of legacy tracks/events, i.e. events that don't
+    //      + tid (only in case of legacy tracks/events, i.e. events that don't
     //      specify an explicit track uuid or use legacy event phases instead of
     //      TrackEvent types), or
     //   b) a default track.
     if (track_uuid_) {
       base::Optional<TrackId> opt_track_id =
-          track_tracker->GetDescriptorTrack(track_uuid_);
+          track_event_tracker_->GetDescriptorTrack(track_uuid_, name_id_);
       if (!opt_track_id) {
-        track_tracker->ReserveDescriptorChildTrack(track_uuid_,
-                                                   /*parent_uuid=*/0, name_id_);
-        opt_track_id = track_tracker->GetDescriptorTrack(track_uuid_);
+        track_event_tracker_->ReserveDescriptorChildTrack(track_uuid_,
+                                                          /*parent_uuid=*/0,
+                                                          name_id_);
+        opt_track_id =
+            track_event_tracker_->GetDescriptorTrack(track_uuid_, name_id_);
       }
       track_id_ = *opt_track_id;
 
@@ -359,7 +368,7 @@
         upid_ = storage_->thread_table().upid()[*utid_];
         track_id_ = track_tracker->InternThreadTrack(*utid_);
       } else {
-        track_id_ = track_tracker->GetOrCreateDefaultDescriptorTrack();
+        track_id_ = track_event_tracker_->GetOrCreateDefaultDescriptorTrack();
       }
     }
 
@@ -520,7 +529,7 @@
       PERFETTO_DCHECK(index < TrackEventData::kMaxNumExtraCounters);
 
       base::Optional<TrackId> track_id =
-          context_->track_tracker->GetDescriptorTrack(*track_uuid_it);
+          track_event_tracker_->GetDescriptorTrack(*track_uuid_it);
       base::Optional<uint32_t> counter_row =
           storage_->counter_track_table().id().IndexOf(*track_id);
 
@@ -558,6 +567,7 @@
           opt_slice_id.value(), event_data_->thread_timestamp,
           kPendingThreadDuration, event_data_->thread_instruction_count,
           kPendingThreadInstructionDelta);
+      MaybeParseFlowEvents();
     }
 
     return util::OkStatus();
@@ -603,11 +613,107 @@
           opt_slice_id.value(), event_data_->thread_timestamp,
           thread_duration_ns, event_data_->thread_instruction_count,
           legacy_event_.thread_instruction_delta());
+      MaybeParseFlowEvents();
     }
 
     return util::OkStatus();
   }
 
+  base::Optional<uint64_t> GetLegacyEventId() {
+    if (legacy_event_.has_unscoped_id())
+      return legacy_event_.unscoped_id();
+    // TODO(andrewbb): Catapult doesn't support global_id and local_id on flow
+    // events. We could add support in trace processor (e.g. because there seem
+    // to be some callsites supplying local_id in chromium), but we would have
+    // to consider the process ID for local IDs and use a separate ID scope for
+    // global_id and unscoped_id.
+    return base::nullopt;
+  }
+
+  util::Status ParseFlowEventV1(char phase) {
+    auto opt_source_id = GetLegacyEventId();
+    if (!opt_source_id) {
+      storage_->IncrementStats(stats::flow_invalid_id);
+      return util::ErrStatus("Invalid id for flow event v1");
+    }
+    FlowId flow_id = context_->flow_tracker->GetFlowIdForV1Event(
+        opt_source_id.value(), category_id_, name_id_);
+    switch (phase) {
+      case 's':
+        context_->flow_tracker->Begin(track_id_, flow_id);
+        break;
+      case 't':
+        context_->flow_tracker->Step(track_id_, flow_id);
+        break;
+      case 'f':
+        context_->flow_tracker->End(track_id_, flow_id,
+                                    legacy_event_.bind_to_enclosing(),
+                                    /* close_flow = */ false);
+        break;
+    }
+    return util::OkStatus();
+  }
+
+  void MaybeParseTrackEventFlows() {
+    if (event_.has_flow_ids()) {
+      auto it = event_.flow_ids();
+      for (; it; ++it) {
+        FlowId flow_id = *it;
+        if (!context_->flow_tracker->IsActive(flow_id)) {
+          context_->flow_tracker->Begin(track_id_, flow_id);
+          continue;
+        }
+        context_->flow_tracker->Step(track_id_, flow_id);
+      }
+    }
+    if (event_.has_terminating_flow_ids()) {
+      auto it = event_.terminating_flow_ids();
+      for (; it; ++it) {
+        FlowId flow_id = *it;
+        if (!context_->flow_tracker->IsActive(flow_id)) {
+          // If we should terminate a flow, do not begin a new one if it's not
+          // active already.
+          continue;
+        }
+        context_->flow_tracker->End(track_id_, flow_id,
+                                    /* bind_enclosing = */ true,
+                                    /* close_flow = */ true);
+      }
+    }
+  }
+
+  void MaybeParseFlowEventV2() {
+    if (!legacy_event_.has_bind_id()) {
+      return;
+    }
+    if (!legacy_event_.has_flow_direction()) {
+      storage_->IncrementStats(stats::flow_without_direction);
+      return;
+    }
+
+    auto bind_id = legacy_event_.bind_id();
+    switch (legacy_event_.flow_direction()) {
+      case LegacyEvent::FLOW_OUT:
+        context_->flow_tracker->Begin(track_id_, bind_id);
+        break;
+      case LegacyEvent::FLOW_INOUT:
+        context_->flow_tracker->Step(track_id_, bind_id);
+        break;
+      case LegacyEvent::FLOW_IN:
+        context_->flow_tracker->End(track_id_, bind_id,
+                                    /* bind_enclosing_slice = */ true,
+                                    /* close_flow = */ false);
+        break;
+      default:
+        storage_->IncrementStats(stats::flow_without_direction);
+    }
+  }
+
+  void MaybeParseFlowEvents() {
+    MaybeParseFlowEventV2();
+    MaybeParseTrackEventFlows();
+  }
+
   util::Status ParseThreadInstantEvent() {
     // Handle instant events as slices with zero duration, so that they end
     // up nested underneath their parent slices.
@@ -702,8 +808,9 @@
       if (!thread_name.size)
         return util::OkStatus();
       auto thread_name_id = storage_->InternString(thread_name);
-      procs->UpdateThreadNameByUtid(*utid_, thread_name_id,
-                                    ThreadNamePriority::kTrackDescriptor);
+      procs->UpdateThreadNameByUtid(
+          *utid_, thread_name_id,
+          ThreadNamePriority::kTrackDescriptorThreadType);
       return util::OkStatus();
     }
     if (strcmp(event_name.c_str(), "process_name") == 0) {
@@ -832,6 +939,10 @@
     if (event_.has_log_message()) {
       log_errors(ParseLogMessage(event_.log_message(), inserter));
     }
+    if (event_.has_chrome_histogram_sample()) {
+      log_errors(
+          ParseHistogramName(event_.chrome_histogram_sample(), inserter));
+    }
 
     log_errors(
         parser_->context_->proto_to_args_table_->InternProtoFieldsIntoArgsTable(
@@ -843,39 +954,6 @@
                        Variadic::UnsignedInteger(*legacy_passthrough_utid_),
                        ArgsTracker::UpdatePolicy::kSkipIfExists);
     }
-
-    // TODO(eseckler): Parse legacy flow events into flow events table once we
-    // have a design for it.
-    if (legacy_event_.has_bind_id()) {
-      inserter->AddArg(parser_->legacy_event_bind_id_key_id_,
-                       Variadic::UnsignedInteger(legacy_event_.bind_id()));
-    }
-
-    if (legacy_event_.bind_to_enclosing()) {
-      inserter->AddArg(parser_->legacy_event_bind_to_enclosing_key_id_,
-                       Variadic::Boolean(true));
-    }
-
-    if (legacy_event_.flow_direction()) {
-      StringId value = kNullStringId;
-      switch (legacy_event_.flow_direction()) {
-        case LegacyEvent::FLOW_IN:
-          value = parser_->flow_direction_value_in_id_;
-          break;
-        case LegacyEvent::FLOW_OUT:
-          value = parser_->flow_direction_value_out_id_;
-          break;
-        case LegacyEvent::FLOW_INOUT:
-          value = parser_->flow_direction_value_inout_id_;
-          break;
-        default:
-          PERFETTO_ELOG("Unknown flow direction: %d",
-                        legacy_event_.flow_direction());
-          break;
-      }
-      inserter->AddArg(parser_->legacy_event_flow_direction_key_id_,
-                       Variadic::String(value));
-    }
   }
 
   util::Status ParseDebugAnnotationArgs(ConstBytes debug_annotation,
@@ -1060,7 +1138,32 @@
     return util::OkStatus();
   }
 
+  util::Status ParseHistogramName(ConstBytes blob, BoundInserter* inserter) {
+    protos::pbzero::ChromeHistogramSample::Decoder sample(blob);
+    if (!sample.has_name_iid()) {
+      PERFETTO_DLOG("name_iid is not set for ChromeHistogramSample");
+      return util::OkStatus();
+    }
+
+    if (sample.has_name()) {
+      return util::ErrStatus(
+          "name is already set for ChromeHistogramSample: only one of name and "
+          "name_iid can be set.");
+    }
+
+    auto* decoder = sequence_state_->LookupInternedMessage<
+        protos::pbzero::InternedData::kHistogramNamesFieldNumber,
+        protos::pbzero::HistogramName>(sample.name_iid());
+    if (!decoder)
+      return util::ErrStatus("HistogramName with invalid name_iid");
+
+    inserter->AddArg(parser_->histogram_name_key_id_,
+                     Variadic::String(storage_->InternString(decoder->name())));
+    return util::OkStatus();
+  }
+
   TraceProcessorContext* context_;
+  TrackEventTracker* track_event_tracker_;
   TraceStorage* storage_;
   TrackEventParser* parser_;
   int64_t ts_;
@@ -1085,8 +1188,10 @@
   base::Optional<UniqueTid> legacy_passthrough_utid_;
 };
 
-TrackEventParser::TrackEventParser(TraceProcessorContext* context)
+TrackEventParser::TrackEventParser(TraceProcessorContext* context,
+                                   TrackEventTracker* track_event_tracker)
     : context_(context),
+      track_event_tracker_(track_event_tracker),
       counter_name_thread_time_id_(
           context->storage->InternString("thread_time")),
       counter_name_thread_instruction_count_id_(
@@ -1137,6 +1242,8 @@
           context->storage->InternString("legacy_event.bind_to_enclosing")),
       legacy_event_flow_direction_key_id_(
           context->storage->InternString("legacy_event.flow_direction")),
+      histogram_name_key_id_(
+          context->storage->InternString("chrome_histogram_sample.name")),
       flow_direction_value_in_id_(context->storage->InternString("in")),
       flow_direction_value_out_id_(context->storage->InternString("out")),
       flow_direction_value_inout_id_(context->storage->InternString("inout")),
@@ -1253,8 +1360,7 @@
 
   // Ensure that the track and its parents are resolved. This may start a new
   // process and/or thread (i.e. new upid/utid).
-  TrackId track_id =
-      *context_->track_tracker->GetDescriptorTrack(decoder.uuid());
+  TrackId track_id = *track_event_tracker_->GetDescriptorTrack(decoder.uuid());
 
   if (decoder.has_thread()) {
     UniqueTid utid = ParseThreadDescriptor(decoder.thread());
diff --git a/src/trace_processor/importers/proto/track_event_parser.h b/src/trace_processor/importers/proto/track_event_parser.h
index 899bcb1..0f8d2a8 100644
--- a/src/trace_processor/importers/proto/track_event_parser.h
+++ b/src/trace_processor/importers/proto/track_event_parser.h
@@ -32,21 +32,23 @@
 }
 
 namespace perfetto {
-
 namespace trace_processor {
 
 // Field numbers to be added to args table automatically via reflection
 //
 // TODO(ddrone): replace with a predicate on field id to import new fields
 // automatically
-static constexpr uint16_t kReflectFields[] = {24, 25, 26, 27, 28, 29, 32};
+static constexpr uint16_t kReflectFields[] = {24, 25, 26, 27, 28, 29, 32,
+                                              33, 34, 35, 38, 39, 40, 41,
+                                              42};
 
 class PacketSequenceStateGeneration;
 class TraceProcessorContext;
+class TrackEventTracker;
 
 class TrackEventParser {
  public:
-  explicit TrackEventParser(TraceProcessorContext* context);
+  TrackEventParser(TraceProcessorContext*, TrackEventTracker*);
 
   void ParseTrackDescriptor(protozero::ConstBytes);
   UniquePid ParseProcessDescriptor(protozero::ConstBytes);
@@ -64,6 +66,7 @@
   void ParseCounterDescriptor(TrackId, protozero::ConstBytes);
 
   TraceProcessorContext* context_;
+  TrackEventTracker* track_event_tracker_;
 
   const StringId counter_name_thread_time_id_;
   const StringId counter_name_thread_instruction_count_id_;
@@ -89,6 +92,7 @@
   const StringId legacy_event_bind_id_key_id_;
   const StringId legacy_event_bind_to_enclosing_key_id_;
   const StringId legacy_event_flow_direction_key_id_;
+  const StringId histogram_name_key_id_;
   const StringId flow_direction_value_in_id_;
   const StringId flow_direction_value_out_id_;
   const StringId flow_direction_value_inout_id_;
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.cc b/src/trace_processor/importers/proto/track_event_tokenizer.cc
index a09f4e2..42e663a 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.cc
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.cc
@@ -21,7 +21,8 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
+#include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/trace_blob_view.h"
@@ -44,8 +45,10 @@
 using protos::pbzero::CounterDescriptor;
 }
 
-TrackEventTokenizer::TrackEventTokenizer(TraceProcessorContext* context)
+TrackEventTokenizer::TrackEventTokenizer(TraceProcessorContext* context,
+                                         TrackEventTracker* track_event_tracker)
     : context_(context),
+      track_event_tracker_(track_event_tracker),
       counter_name_thread_time_id_(
           context_->storage->InternString("thread_time")),
       counter_name_thread_instruction_count_id_(
@@ -84,7 +87,7 @@
       TokenizeThreadDescriptor(state, thread);
     }
 
-    context_->track_tracker->ReserveDescriptorThreadTrack(
+    track_event_tracker_->ReserveDescriptorThreadTrack(
         track.uuid(), track.parent_uuid(), name_id,
         static_cast<uint32_t>(thread.pid()),
         static_cast<uint32_t>(thread.tid()), packet_timestamp);
@@ -98,7 +101,7 @@
       return ModuleResult::Handled();
     }
 
-    context_->track_tracker->ReserveDescriptorProcessTrack(
+    track_event_tracker_->ReserveDescriptorProcessTrack(
         track.uuid(), name_id, static_cast<uint32_t>(process.pid()),
         packet_timestamp);
   } else if (track.has_counter()) {
@@ -125,7 +128,7 @@
     // threads, in which case it has to use absolute values on a different
     // track_uuid. Right now these absolute values are imported onto a separate
     // counter track than the other thread's regular thread time values.)
-    if (name_id == kNullStringId) {
+    if (name_id.is_null()) {
       switch (counter.type()) {
         case CounterDescriptor::COUNTER_UNSPECIFIED:
           break;
@@ -138,16 +141,16 @@
       }
     }
 
-    context_->track_tracker->ReserveDescriptorCounterTrack(
+    track_event_tracker_->ReserveDescriptorCounterTrack(
         track.uuid(), track.parent_uuid(), name_id, category_id,
         counter.unit_multiplier(), counter.is_incremental(),
         packet.trusted_packet_sequence_id());
   } else {
-    context_->track_tracker->ReserveDescriptorChildTrack(
+    track_event_tracker_->ReserveDescriptorChildTrack(
         track.uuid(), track.parent_uuid(), name_id);
   }
 
-  // Let ProtoTraceTokenizer forward the packet to the parser.
+  // Let ProtoTraceReader forward the packet to the parser.
   return ModuleResult::Ignored();
 }
 
@@ -173,7 +176,7 @@
   protos::pbzero::ThreadDescriptor::Decoder thread(packet.thread_descriptor());
   TokenizeThreadDescriptor(state, thread);
 
-  // Let ProtoTraceTokenizer forward the packet to the parser.
+  // Let ProtoTraceReader forward the packet to the parser.
   return ModuleResult::Ignored();
 }
 
@@ -302,7 +305,7 @@
     }
 
     base::Optional<int64_t> value =
-        context_->track_tracker->ConvertToAbsoluteCounterValue(
+        track_event_tracker_->ConvertToAbsoluteCounterValue(
             track_uuid, packet.trusted_packet_sequence_id(),
             event.counter_value());
 
@@ -350,7 +353,7 @@
         return;
       }
       base::Optional<int64_t> value =
-          context_->track_tracker->ConvertToAbsoluteCounterValue(
+          track_event_tracker_->ConvertToAbsoluteCounterValue(
               *track_uuid_it, packet.trusted_packet_sequence_id(), *value_it);
       if (!value) {
         PERFETTO_DLOG("Ignoring TrackEvent with invalid extra counter track");
diff --git a/src/trace_processor/importers/proto/track_event_tokenizer.h b/src/trace_processor/importers/proto/track_event_tokenizer.h
index 6657a48..0595a59 100644
--- a/src/trace_processor/importers/proto/track_event_tokenizer.h
+++ b/src/trace_processor/importers/proto/track_event_tokenizer.h
@@ -38,10 +38,11 @@
 class PacketSequenceState;
 class TraceProcessorContext;
 class TraceBlobView;
+class TrackEventTracker;
 
 class TrackEventTokenizer {
  public:
-  explicit TrackEventTokenizer(TraceProcessorContext* context);
+  explicit TrackEventTokenizer(TraceProcessorContext*, TrackEventTracker*);
 
   ModuleResult TokenizeTrackDescriptorPacket(
       PacketSequenceState* state,
@@ -61,6 +62,7 @@
       const protos::pbzero::ThreadDescriptor_Decoder&);
 
   TraceProcessorContext* context_;
+  TrackEventTracker* track_event_tracker_;
 
   const StringId counter_name_thread_time_id_;
   const StringId counter_name_thread_instruction_count_id_;
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
new file mode 100644
index 0000000..8e84e14
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/importers/proto/track_event_tracker.h"
+
+#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// static
+constexpr uint64_t TrackEventTracker::kDefaultDescriptorTrackUuid;
+
+TrackEventTracker::TrackEventTracker(TraceProcessorContext* context)
+    : source_key_(context->storage->InternString("source")),
+      source_id_key_(context->storage->InternString("source_id")),
+      is_root_in_scope_key_(context->storage->InternString("is_root_in_scope")),
+      category_key_(context->storage->InternString("category")),
+      descriptor_source_(context->storage->InternString("descriptor")),
+      default_descriptor_track_name_(
+          context->storage->InternString("Default Track")),
+      context_(context) {}
+
+void TrackEventTracker::ReserveDescriptorProcessTrack(uint64_t uuid,
+                                                      StringId name,
+                                                      uint32_t pid,
+                                                      int64_t timestamp) {
+  DescriptorTrackReservation reservation;
+  reservation.min_timestamp = timestamp;
+  reservation.pid = pid;
+  reservation.name = name;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted)
+    return;
+
+  if (!it->second.IsForSameTrack(reservation)) {
+    // Process tracks should not be reassigned to a different pid later (neither
+    // should the type of the track change).
+    PERFETTO_DLOG("New track reservation for process track with uuid %" PRIu64
+                  " doesn't match earlier one",
+                  uuid);
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
+}
+
+void TrackEventTracker::ReserveDescriptorThreadTrack(uint64_t uuid,
+                                                     uint64_t parent_uuid,
+                                                     StringId name,
+                                                     uint32_t pid,
+                                                     uint32_t tid,
+                                                     int64_t timestamp) {
+  DescriptorTrackReservation reservation;
+  reservation.min_timestamp = timestamp;
+  reservation.parent_uuid = parent_uuid;
+  reservation.pid = pid;
+  reservation.tid = tid;
+  reservation.name = name;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted)
+    return;
+
+  if (!it->second.IsForSameTrack(reservation)) {
+    // Thread tracks should not be reassigned to a different pid/tid later
+    // (neither should the type of the track change).
+    PERFETTO_DLOG("New track reservation for thread track with uuid %" PRIu64
+                  " doesn't match earlier one",
+                  uuid);
+    context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+    return;
+  }
+
+  it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
+}
+
+void TrackEventTracker::ReserveDescriptorCounterTrack(
+    uint64_t uuid,
+    uint64_t parent_uuid,
+    StringId name,
+    StringId category,
+    int64_t unit_multiplier,
+    bool is_incremental,
+    uint32_t packet_sequence_id) {
+  DescriptorTrackReservation reservation;
+  reservation.parent_uuid = parent_uuid;
+  reservation.is_counter = true;
+  reservation.name = name;
+  reservation.category = category;
+  reservation.unit_multiplier = unit_multiplier;
+  reservation.is_incremental = is_incremental;
+  // Incrementally encoded counters are only valid on a single sequence.
+  if (is_incremental)
+    reservation.packet_sequence_id = packet_sequence_id;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted || it->second.IsForSameTrack(reservation))
+    return;
+
+  // Counter tracks should not be reassigned to a different parent track later
+  // (neither should the type of the track change).
+  PERFETTO_DLOG("New track reservation for counter track with uuid %" PRIu64
+                " doesn't match earlier one",
+                uuid);
+  context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+}
+
+void TrackEventTracker::ReserveDescriptorChildTrack(uint64_t uuid,
+                                                    uint64_t parent_uuid,
+                                                    StringId name) {
+  DescriptorTrackReservation reservation;
+  reservation.parent_uuid = parent_uuid;
+  reservation.name = name;
+
+  std::map<uint64_t, DescriptorTrackReservation>::iterator it;
+  bool inserted;
+  std::tie(it, inserted) =
+      reserved_descriptor_tracks_.insert(std::make_pair<>(uuid, reservation));
+
+  if (inserted || it->second.IsForSameTrack(reservation))
+    return;
+
+  // Child tracks should not be reassigned to a different parent track later
+  // (neither should the type of the track change).
+  PERFETTO_DLOG("New track reservation for child track with uuid %" PRIu64
+                " doesn't match earlier one",
+                uuid);
+  context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
+}
+
+base::Optional<TrackId> TrackEventTracker::GetDescriptorTrack(
+    uint64_t uuid,
+    StringId event_name) {
+  base::Optional<TrackId> track_id = GetDescriptorTrackImpl(uuid);
+  if (!track_id || event_name.is_null())
+    return track_id;
+
+  // Update the name of the track if unset and the track is not the primary
+  // track of a process/thread or a counter track.
+  auto* tracks = context_->storage->mutable_track_table();
+  uint32_t row = *tracks->id().IndexOf(*track_id);
+  if (!tracks->name()[row].is_null())
+    return track_id;
+
+  // Check reservation for track type.
+  auto reservation_it = reserved_descriptor_tracks_.find(uuid);
+  PERFETTO_CHECK(reservation_it != reserved_descriptor_tracks_.end());
+
+  if (reservation_it->second.pid || reservation_it->second.tid ||
+      reservation_it->second.is_counter) {
+    return track_id;
+  }
+  tracks->mutable_name()->Set(row, event_name);
+  return track_id;
+}
+
+base::Optional<TrackId> TrackEventTracker::GetDescriptorTrackImpl(
+    uint64_t uuid) {
+  auto it = descriptor_tracks_.find(uuid);
+  if (it != descriptor_tracks_.end())
+    return it->second;
+
+  base::Optional<ResolvedDescriptorTrack> resolved_track =
+      ResolveDescriptorTrack(uuid, nullptr);
+  if (!resolved_track)
+    return base::nullopt;
+
+  // The reservation must exist as |resolved_track| would have been nullopt
+  // otherwise.
+  auto reserved_it = reserved_descriptor_tracks_.find(uuid);
+  PERFETTO_CHECK(reserved_it != reserved_descriptor_tracks_.end());
+
+  const auto& reservation = reserved_it->second;
+  TrackId track_id = CreateTrackFromResolved(*resolved_track);
+  descriptor_tracks_[uuid] = track_id;
+
+  auto args = context_->args_tracker->AddArgsTo(track_id);
+  args.AddArg(source_key_, Variadic::String(descriptor_source_))
+      .AddArg(source_id_key_, Variadic::Integer(static_cast<int64_t>(uuid)))
+      .AddArg(is_root_in_scope_key_,
+              Variadic::Boolean(resolved_track->is_root_in_scope()));
+  if (!reservation.category.is_null())
+    args.AddArg(category_key_, Variadic::String(reservation.category));
+
+  if (reservation.name.is_null())
+    return track_id;
+
+  // Initialize the track name here, so that, if a name was given in the
+  // reservation, it is set immediately after resolution takes place.
+  auto* tracks = context_->storage->mutable_track_table();
+  tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id),
+                              reservation.name);
+  return track_id;
+}
+
+TrackId TrackEventTracker::CreateTrackFromResolved(
+    const ResolvedDescriptorTrack& track) {
+  if (track.is_root_in_scope()) {
+    switch (track.scope()) {
+      case ResolvedDescriptorTrack::Scope::kThread:
+        return context_->track_tracker->InternThreadTrack(track.utid());
+      case ResolvedDescriptorTrack::Scope::kProcess:
+        return context_->track_tracker->InternProcessTrack(track.upid());
+      case ResolvedDescriptorTrack::Scope::kGlobal:
+        // Will be handled below.
+        break;
+    }
+  }
+
+  switch (track.scope()) {
+    case ResolvedDescriptorTrack::Scope::kThread: {
+      if (track.is_counter()) {
+        tables::ThreadCounterTrackTable::Row row;
+        row.utid = track.utid();
+
+        auto* thread_counter_tracks =
+            context_->storage->mutable_thread_counter_track_table();
+        return thread_counter_tracks->Insert(row).id;
+      }
+
+      tables::ThreadTrackTable::Row row;
+      row.utid = track.utid();
+
+      auto* thread_tracks = context_->storage->mutable_thread_track_table();
+      return thread_tracks->Insert(row).id;
+    }
+    case ResolvedDescriptorTrack::Scope::kProcess: {
+      if (track.is_counter()) {
+        tables::ProcessCounterTrackTable::Row row;
+        row.upid = track.upid();
+
+        auto* process_counter_tracks =
+            context_->storage->mutable_process_counter_track_table();
+        return process_counter_tracks->Insert(row).id;
+      }
+
+      tables::ProcessTrackTable::Row row;
+      row.upid = track.upid();
+
+      auto* process_tracks = context_->storage->mutable_process_track_table();
+      return process_tracks->Insert(row).id;
+    }
+    case ResolvedDescriptorTrack::Scope::kGlobal: {
+      if (track.is_counter())
+        return context_->storage->mutable_counter_track_table()->Insert({}).id;
+      return context_->storage->mutable_track_table()->Insert({}).id;
+    }
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+base::Optional<TrackEventTracker::ResolvedDescriptorTrack>
+TrackEventTracker::ResolveDescriptorTrack(
+    uint64_t uuid,
+    std::vector<uint64_t>* descendent_uuids) {
+  auto it = resolved_descriptor_tracks_.find(uuid);
+  if (it != resolved_descriptor_tracks_.end())
+    return it->second;
+
+  auto reservation_it = reserved_descriptor_tracks_.find(uuid);
+  if (reservation_it == reserved_descriptor_tracks_.end())
+    return base::nullopt;
+
+  auto resolved_track = ResolveDescriptorTrackImpl(uuid, reservation_it->second,
+                                                   descendent_uuids);
+  resolved_descriptor_tracks_[uuid] = resolved_track;
+  return resolved_track;
+}
+
+TrackEventTracker::ResolvedDescriptorTrack
+TrackEventTracker::ResolveDescriptorTrackImpl(
+    uint64_t uuid,
+    const DescriptorTrackReservation& reservation,
+    std::vector<uint64_t>* descendent_uuids) {
+  static constexpr size_t kMaxAncestors = 10;
+
+  // Try to resolve any parent tracks recursively, too.
+  base::Optional<ResolvedDescriptorTrack> parent_resolved_track;
+  if (reservation.parent_uuid) {
+    // Input data may contain loops or extremely long ancestor track chains. To
+    // avoid stack overflow in these situations, we keep track of the ancestors
+    // seen in the recursion.
+    std::unique_ptr<std::vector<uint64_t>> owned_descendent_uuids;
+    if (!descendent_uuids) {
+      owned_descendent_uuids.reset(new std::vector<uint64_t>());
+      descendent_uuids = owned_descendent_uuids.get();
+    }
+    descendent_uuids->push_back(uuid);
+
+    if (descendent_uuids->size() > kMaxAncestors) {
+      PERFETTO_ELOG(
+          "Too many ancestors in parent_track_uuid hierarchy at track %" PRIu64
+          " with parent %" PRIu64,
+          uuid, reservation.parent_uuid);
+    } else if (std::find(descendent_uuids->begin(), descendent_uuids->end(),
+                         reservation.parent_uuid) != descendent_uuids->end()) {
+      PERFETTO_ELOG(
+          "Loop detected in parent_track_uuid hierarchy at track %" PRIu64
+          " with parent %" PRIu64,
+          uuid, reservation.parent_uuid);
+    } else {
+      parent_resolved_track =
+          ResolveDescriptorTrack(reservation.parent_uuid, descendent_uuids);
+      if (!parent_resolved_track) {
+        PERFETTO_ELOG("Unknown parent track %" PRIu64 " for track %" PRIu64,
+                      reservation.parent_uuid, uuid);
+      }
+    }
+
+    descendent_uuids->pop_back();
+    if (owned_descendent_uuids)
+      descendent_uuids = nullptr;
+  }
+
+  if (reservation.tid) {
+    UniqueTid utid = context_->process_tracker->UpdateThread(*reservation.tid,
+                                                             *reservation.pid);
+    auto it_and_inserted =
+        descriptor_uuids_by_utid_.insert(std::make_pair<>(utid, uuid));
+    if (!it_and_inserted.second) {
+      // We already saw a another track with a different uuid for this thread.
+      // Since there should only be one descriptor track for each thread, we
+      // assume that its tid was reused. So, start a new thread.
+      uint64_t old_uuid = it_and_inserted.first->second;
+      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
+
+      PERFETTO_DLOG("Detected tid reuse (pid: %" PRIu32 " tid: %" PRIu32
+                    ") from track descriptors (old uuid: %" PRIu64
+                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
+                    *reservation.pid, *reservation.tid, old_uuid, uuid,
+                    reservation.min_timestamp);
+
+      utid = context_->process_tracker->StartNewThread(base::nullopt,
+                                                       *reservation.tid);
+
+      // Associate the new thread with its process.
+      PERFETTO_CHECK(context_->process_tracker->UpdateThread(
+                         *reservation.tid, *reservation.pid) == utid);
+
+      descriptor_uuids_by_utid_[utid] = uuid;
+    }
+    return ResolvedDescriptorTrack::Thread(utid, false /* is_counter */,
+                                           true /* is_root*/);
+  }
+
+  if (reservation.pid) {
+    UniquePid upid =
+        context_->process_tracker->GetOrCreateProcess(*reservation.pid);
+    auto it_and_inserted =
+        descriptor_uuids_by_upid_.insert(std::make_pair<>(upid, uuid));
+    if (!it_and_inserted.second) {
+      // We already saw a another track with a different uuid for this process.
+      // Since there should only be one descriptor track for each process, we
+      // assume that its pid was reused. So, start a new process.
+      uint64_t old_uuid = it_and_inserted.first->second;
+      PERFETTO_DCHECK(old_uuid != uuid);  // Every track is only resolved once.
+
+      PERFETTO_DLOG("Detected pid reuse (pid: %" PRIu32
+                    ") from track descriptors (old uuid: %" PRIu64
+                    " new uuid: %" PRIu64 " timestamp: %" PRId64 ")",
+                    *reservation.pid, old_uuid, uuid,
+                    reservation.min_timestamp);
+
+      upid = context_->process_tracker->StartNewProcess(
+          base::nullopt, base::nullopt, *reservation.pid, kNullStringId);
+
+      descriptor_uuids_by_upid_[upid] = uuid;
+    }
+    return ResolvedDescriptorTrack::Process(upid, false /* is_counter */,
+                                            true /* is_root*/);
+  }
+
+  if (parent_resolved_track) {
+    switch (parent_resolved_track->scope()) {
+      case ResolvedDescriptorTrack::Scope::kThread:
+        // If parent is a thread track, create another thread-associated track.
+        return ResolvedDescriptorTrack::Thread(parent_resolved_track->utid(),
+                                               reservation.is_counter,
+                                               false /* is_root*/);
+      case ResolvedDescriptorTrack::Scope::kProcess:
+        // If parent is a process track, create another process-associated
+        // track.
+        return ResolvedDescriptorTrack::Process(parent_resolved_track->upid(),
+                                                reservation.is_counter,
+                                                false /* is_root*/);
+      case ResolvedDescriptorTrack::Scope::kGlobal:
+        break;
+    }
+  }
+
+  // Otherwise create a global track.
+
+  // The global track with no uuid is the default global track (e.g. for
+  // global instant events). Any other global tracks are considered children
+  // of the default track.
+  bool is_root_in_scope = !parent_resolved_track;
+  if (!parent_resolved_track && uuid) {
+    // Detect loops where the default track has a parent that itself is a
+    // global track (and thus should be parent of the default track).
+    if (descendent_uuids &&
+        std::find(descendent_uuids->begin(), descendent_uuids->end(),
+                  kDefaultDescriptorTrackUuid) != descendent_uuids->end()) {
+      PERFETTO_ELOG(
+          "Loop detected in parent_track_uuid hierarchy at track %" PRIu64
+          " with parent %" PRIu64,
+          uuid, kDefaultDescriptorTrackUuid);
+    } else {
+      // This track will be implicitly a child of the default global track.
+      is_root_in_scope = false;
+    }
+  }
+  return ResolvedDescriptorTrack::Global(reservation.is_counter,
+                                         is_root_in_scope);
+}
+
+TrackId TrackEventTracker::GetOrCreateDefaultDescriptorTrack() {
+  // If the default track was already reserved (e.g. because a producer emitted
+  // a descriptor for it) or created, resolve and return it.
+  base::Optional<TrackId> track_id =
+      GetDescriptorTrack(kDefaultDescriptorTrackUuid);
+  if (track_id)
+    return *track_id;
+
+  // Otherwise reserve a new track and resolve it.
+  ReserveDescriptorChildTrack(kDefaultDescriptorTrackUuid, /*parent_uuid=*/0,
+                              default_descriptor_track_name_);
+  return *GetDescriptorTrack(kDefaultDescriptorTrackUuid);
+}
+
+base::Optional<int64_t> TrackEventTracker::ConvertToAbsoluteCounterValue(
+    uint64_t counter_track_uuid,
+    uint32_t packet_sequence_id,
+    int64_t value) {
+  auto reservation_it = reserved_descriptor_tracks_.find(counter_track_uuid);
+  if (reservation_it == reserved_descriptor_tracks_.end()) {
+    PERFETTO_DLOG("Unknown counter track with uuid %" PRIu64,
+                  counter_track_uuid);
+    return base::nullopt;
+  }
+
+  DescriptorTrackReservation& reservation = reservation_it->second;
+  if (!reservation.is_counter) {
+    PERFETTO_DLOG("Track with uuid %" PRIu64 " is not a counter track",
+                  counter_track_uuid);
+    return base::nullopt;
+  }
+
+  if (reservation.unit_multiplier > 0)
+    value *= reservation.unit_multiplier;
+
+  if (reservation.is_incremental) {
+    if (reservation.packet_sequence_id != packet_sequence_id) {
+      PERFETTO_DLOG(
+          "Incremental counter track with uuid %" PRIu64
+          " was updated from the wrong packet sequence (expected: %" PRIu32
+          " got:%" PRIu32 ")",
+          counter_track_uuid, reservation.packet_sequence_id,
+          packet_sequence_id);
+      return base::nullopt;
+    }
+
+    reservation.latest_value += value;
+    value = reservation.latest_value;
+  }
+
+  return value;
+}
+
+void TrackEventTracker::OnIncrementalStateCleared(uint32_t packet_sequence_id) {
+  // TODO(eseckler): Improve on the runtime complexity of this. At O(hundreds)
+  // of packet sequences, incremental state clearing at O(trace second), and
+  // total number of tracks in O(thousands), a linear scan through all tracks
+  // here might not be fast enough.
+  for (auto& entry : reserved_descriptor_tracks_) {
+    DescriptorTrackReservation& reservation = entry.second;
+    // Only consider incremental counter tracks for current sequence.
+    if (!reservation.is_counter || !reservation.is_incremental ||
+        reservation.packet_sequence_id != packet_sequence_id) {
+      continue;
+    }
+    // Reset their value to 0, see CounterDescriptor's |is_incremental|.
+    reservation.latest_value = 0;
+  }
+}
+
+TrackEventTracker::ResolvedDescriptorTrack
+TrackEventTracker::ResolvedDescriptorTrack::Process(UniquePid upid,
+                                                    bool is_counter,
+                                                    bool is_root) {
+  ResolvedDescriptorTrack track;
+  track.scope_ = Scope::kProcess;
+  track.is_counter_ = is_counter;
+  track.is_root_in_scope_ = is_root;
+  track.upid_ = upid;
+  return track;
+}
+
+TrackEventTracker::ResolvedDescriptorTrack
+TrackEventTracker::ResolvedDescriptorTrack::Thread(UniqueTid utid,
+                                                   bool is_counter,
+                                                   bool is_root) {
+  ResolvedDescriptorTrack track;
+  track.scope_ = Scope::kThread;
+  track.is_counter_ = is_counter;
+  track.is_root_in_scope_ = is_root;
+  track.utid_ = utid;
+  return track;
+}
+
+TrackEventTracker::ResolvedDescriptorTrack
+TrackEventTracker::ResolvedDescriptorTrack::Global(bool is_counter,
+                                                   bool is_root) {
+  ResolvedDescriptorTrack track;
+  track.scope_ = Scope::kGlobal;
+  track.is_counter_ = is_counter;
+  track.is_root_in_scope_ = is_root;
+  return track;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/track_event_tracker.h b/src/trace_processor/importers/proto/track_event_tracker.h
new file mode 100644
index 0000000..fd2721c
--- /dev/null
+++ b/src/trace_processor/importers/proto/track_event_tracker.h
@@ -0,0 +1,238 @@
+/*
+ * 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_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
+
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Tracks and stores tracks based on track types, ids and scopes.
+class TrackEventTracker {
+ public:
+  explicit TrackEventTracker(TraceProcessorContext*);
+
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // process's |pid|. This is called during tokenization. If a reservation for
+  // the same |uuid| already exists, verifies that the present reservation
+  // matches the new one.
+  //
+  // The track will be resolved to the process track (see InternProcessTrack())
+  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
+  // time, |pid| will also be resolved to a |upid|.
+  void ReserveDescriptorProcessTrack(uint64_t uuid,
+                                     StringId name,
+                                     uint32_t pid,
+                                     int64_t timestamp);
+
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // thread's |pid| and |tid|. This is called during tokenization. If a
+  // reservation for the same |uuid| already exists, verifies that the present
+  // reservation matches the new one.
+  //
+  // The track will be resolved to the thread track (see InternThreadTrack())
+  // upon the first call to GetDescriptorTrack() with the same |uuid|. At this
+  // time, |pid| will also be resolved to a |upid|.
+  void ReserveDescriptorThreadTrack(uint64_t uuid,
+                                    uint64_t parent_uuid,
+                                    StringId name,
+                                    uint32_t pid,
+                                    uint32_t tid,
+                                    int64_t timestamp);
+
+  // Associate a TrackDescriptor track identified by the given |uuid| with a
+  // parent track (usually a process- or thread-associated track). This is
+  // called during tokenization. If a reservation for the same |uuid| already
+  // exists, will attempt to update it.
+  //
+  // The track will be created upon the first call to GetDescriptorTrack() with
+  // the same |uuid|. If |parent_uuid| is 0, the track will become a global
+  // track. Otherwise, it will become a new track of the same type as its parent
+  // track.
+  void ReserveDescriptorChildTrack(uint64_t uuid,
+                                   uint64_t parent_uuid,
+                                   StringId name);
+
+  // Associate a counter-type TrackDescriptor track identified by the given
+  // |uuid| with a parent track (usually a process or thread track). This is
+  // called during tokenization. If a reservation for the same |uuid| already
+  // exists, will attempt to update it. The provided |category| will be stored
+  // into the track's args.
+  //
+  // If |is_incremental| is true, the counter will only be valid on the packet
+  // sequence identified by |packet_sequence_id|. |unit_multiplier| is an
+  // optional multiplication factor applied to counter values. Values for the
+  // counter will be translated during tokenization via
+  // ConvertToAbsoluteCounterValue().
+  //
+  // The track will be created upon the first call to GetDescriptorTrack() with
+  // the same |uuid|. If |parent_uuid| is 0, the track will become a global
+  // track. Otherwise, it will become a new counter track for the same
+  // process/thread as its parent track.
+  void ReserveDescriptorCounterTrack(uint64_t uuid,
+                                     uint64_t parent_uuid,
+                                     StringId name,
+                                     StringId category,
+                                     int64_t unit_multiplier,
+                                     bool is_incremental,
+                                     uint32_t packet_sequence_id);
+
+  // Returns the ID of the track for the TrackDescriptor with the given |uuid|.
+  // This is called during parsing. The first call to GetDescriptorTrack() for
+  // each |uuid| resolves and inserts the track (and its parent tracks,
+  // following the parent_uuid chain recursively) based on reservations made for
+  // the |uuid|. If the track is a child track and doesn't have a name yet,
+  // updates the track's name to event_name. Returns nullopt if no track for a
+  // descriptor with this |uuid| has been reserved.
+  // TODO(lalitm): this method needs to be split up and moved back to
+  // TrackTracker.
+  base::Optional<TrackId> GetDescriptorTrack(
+      uint64_t uuid,
+      StringId event_name = kNullStringId);
+
+  // Converts the given counter value to an absolute value in the unit of the
+  // counter, applying incremental delta encoding or unit multipliers as
+  // necessary. If the counter uses incremental encoding, |packet_sequence_id|
+  // must match the one in its track reservation. Returns base::nullopt if the
+  // counter track is unknown or an invalid |packet_sequence_id| was passed.
+  base::Optional<int64_t> ConvertToAbsoluteCounterValue(
+      uint64_t counter_track_uuid,
+      uint32_t packet_sequence_id,
+      int64_t value);
+
+  // Returns the ID of the implicit trace-global default TrackDescriptor track.
+  // TODO(lalitm): this method needs to be moved back to TrackTracker once
+  // GetDescriptorTrack is moved back.
+  TrackId GetOrCreateDefaultDescriptorTrack();
+
+  // Called by ProtoTraceReader whenever incremental state is cleared on a
+  // packet sequence. Resets counter values for any incremental counters of
+  // the sequence identified by |packet_sequence_id|.
+  void OnIncrementalStateCleared(uint32_t packet_sequence_id);
+
+ private:
+  struct DescriptorTrackReservation {
+    uint64_t parent_uuid = 0;
+    base::Optional<uint32_t> pid;
+    base::Optional<uint32_t> tid;
+    int64_t min_timestamp = 0;  // only set if |pid| and/or |tid| is set.
+    StringId name = kNullStringId;
+
+    // For counter tracks.
+    bool is_counter = false;
+    StringId category = kNullStringId;
+    int64_t unit_multiplier = 1;
+    bool is_incremental = false;
+    uint32_t packet_sequence_id = 0;
+    int64_t latest_value = 0;
+
+    // Whether |other| is a valid descriptor for this track reservation. A track
+    // should always remain nested underneath its original parent.
+    bool IsForSameTrack(const DescriptorTrackReservation& other) {
+      // Note that |min_timestamp|, |latest_value|, and |name| are ignored for
+      // this comparison.
+      return std::tie(parent_uuid, pid, tid, is_counter, category,
+                      unit_multiplier, is_incremental, packet_sequence_id) ==
+             std::tie(other.parent_uuid, pid, tid, is_counter, category,
+                      unit_multiplier, is_incremental, packet_sequence_id);
+    }
+  };
+
+  class ResolvedDescriptorTrack {
+   public:
+    enum class Scope {
+      kThread,
+      kProcess,
+      kGlobal,
+    };
+
+    static ResolvedDescriptorTrack Process(UniquePid upid,
+                                           bool is_counter,
+                                           bool is_root);
+    static ResolvedDescriptorTrack Thread(UniqueTid utid,
+                                          bool is_counter,
+                                          bool is_root);
+    static ResolvedDescriptorTrack Global(bool is_counter, bool is_root);
+
+    Scope scope() const { return scope_; }
+    bool is_counter() const { return is_counter_; }
+    UniqueTid utid() const {
+      PERFETTO_DCHECK(scope() == Scope::kThread);
+      return utid_;
+    }
+    UniquePid upid() const {
+      PERFETTO_DCHECK(scope() == Scope::kProcess);
+      return upid_;
+    }
+    UniqueTid is_root_in_scope() const { return is_root_in_scope_; }
+
+   private:
+    Scope scope_;
+    bool is_counter_;
+    bool is_root_in_scope_;
+
+    // Only set when |scope| == |Scope::kThread|.
+    UniqueTid utid_;
+
+    // Only set when |scope| == |Scope::kProcess|.
+    UniquePid upid_;
+  };
+
+  base::Optional<TrackId> GetDescriptorTrackImpl(uint64_t uuid);
+  TrackId CreateTrackFromResolved(const ResolvedDescriptorTrack&);
+  base::Optional<ResolvedDescriptorTrack> ResolveDescriptorTrack(
+      uint64_t uuid,
+      std::vector<uint64_t>* descendent_uuids);
+  ResolvedDescriptorTrack ResolveDescriptorTrackImpl(
+      uint64_t uuid,
+      const DescriptorTrackReservation&,
+      std::vector<uint64_t>* descendent_uuids);
+
+  static constexpr uint64_t kDefaultDescriptorTrackUuid = 0u;
+
+  std::map<UniqueTid, TrackId> thread_tracks_;
+  std::map<UniquePid, TrackId> process_tracks_;
+
+  std::map<uint64_t /* uuid */, DescriptorTrackReservation>
+      reserved_descriptor_tracks_;
+  std::map<uint64_t /* uuid */, ResolvedDescriptorTrack>
+      resolved_descriptor_tracks_;
+  std::map<uint64_t /* uuid */, TrackId> descriptor_tracks_;
+
+  // Stores the descriptor uuid used for the primary process/thread track
+  // for the given upid / utid. Used for pid/tid reuse detection.
+  std::map<UniquePid, uint64_t /*uuid*/> descriptor_uuids_by_upid_;
+  std::map<UniqueTid, uint64_t /*uuid*/> descriptor_uuids_by_utid_;
+
+  const StringId source_key_ = kNullStringId;
+  const StringId source_id_key_ = kNullStringId;
+  const StringId is_root_in_scope_key_ = kNullStringId;
+  const StringId category_key_ = kNullStringId;
+
+  const StringId descriptor_source_ = kNullStringId;
+
+  const StringId default_descriptor_track_name_ = kNullStringId;
+
+  TraceProcessorContext* const context_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_TRACK_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker.h b/src/trace_processor/importers/syscalls/syscall_tracker.h
index c31ffe8..666a220 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker.h
+++ b/src/trace_processor/importers/syscalls/syscall_tracker.h
@@ -45,7 +45,7 @@
  public:
   SyscallTracker(const SyscallTracker&) = delete;
   SyscallTracker& operator=(const SyscallTracker&) = delete;
-  virtual ~SyscallTracker();
+  ~SyscallTracker() override;
   static SyscallTracker* GetOrCreate(TraceProcessorContext* context) {
     if (!context->syscall_tracker) {
       context->syscall_tracker.reset(new SyscallTracker(context));
diff --git a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
index 17733eb..d18792b 100644
--- a/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
+++ b/src/trace_processor/importers/syscalls/syscall_tracker_unittest.cc
@@ -24,6 +24,7 @@
 namespace {
 
 using ::testing::_;
+using ::testing::DoAll;
 using ::testing::Return;
 using ::testing::SaveArg;
 
diff --git a/src/trace_processor/importers/systrace/systrace_line_parser.cc b/src/trace_processor/importers/systrace/systrace_line_parser.cc
index 8654eef..5c25461 100644
--- a/src/trace_processor/importers/systrace/systrace_line_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_line_parser.cc
@@ -40,7 +40,10 @@
     : context_(ctx),
       sched_wakeup_name_id_(ctx->storage->InternString("sched_wakeup")),
       cpuidle_name_id_(ctx->storage->InternString("cpuidle")),
-      workqueue_name_id_(ctx->storage->InternString("workqueue")) {}
+      workqueue_name_id_(ctx->storage->InternString("workqueue")),
+      sched_blocked_reason_id_(
+          ctx->storage->InternString("sched_blocked_reason")),
+      io_wait_id_(ctx->storage->InternString("io_wait")) {}
 
 util::Status SystraceLineParser::ParseLine(const SystraceLine& line) {
   auto utid = context_->process_tracker->UpdateThreadName(
@@ -218,6 +221,18 @@
       return util::Status("Could not convert target");
     }
     context_->event_tracker->PushCounter(line.ts, target.value(), track);
+  } else if (line.event_name == "sched_blocked_reason") {
+    uint32_t wakee_pid = *base::StringToUInt32(args["pid"]);
+    auto wakee_utid = context_->process_tracker->GetOrCreateThread(wakee_pid);
+
+    InstantId id = context_->event_tracker->PushInstant(
+        line.ts, sched_blocked_reason_id_, wakee_utid, RefType::kRefUtid,
+        false);
+
+    auto inserter = context_->args_tracker->AddArgsTo(id);
+    bool io_wait = *base::StringToInt32(args["iowait"]);
+    inserter.AddArg(io_wait_id_, Variadic::Boolean(io_wait));
+    context_->args_tracker->Flush();
   }
 
   return util::OkStatus();
diff --git a/src/trace_processor/importers/systrace/systrace_line_parser.h b/src/trace_processor/importers/systrace/systrace_line_parser.h
index bd8adc0..9024fd4 100644
--- a/src/trace_processor/importers/systrace/systrace_line_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_line_parser.h
@@ -37,6 +37,8 @@
   const StringId sched_wakeup_name_id_ = kNullStringId;
   const StringId cpuidle_name_id_ = kNullStringId;
   const StringId workqueue_name_id_ = kNullStringId;
+  const StringId sched_blocked_reason_id_ = kNullStringId;
+  const StringId io_wait_id_ = kNullStringId;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index 6e8ac7c..02f1d14 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -21,6 +21,7 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -28,7 +29,8 @@
 SystraceParser::SystraceParser(TraceProcessorContext* ctx)
     : context_(ctx),
       lmk_id_(ctx->storage->InternString("mem.lmk")),
-      screen_state_id_(ctx->storage->InternString("ScreenState")) {}
+      screen_state_id_(ctx->storage->InternString("ScreenState")),
+      cookie_id_(ctx->storage->InternString("cookie")) {}
 
 SystraceParser::~SystraceParser() = default;
 
@@ -78,13 +80,13 @@
   ParseSystracePoint(ts, pid, point);
 }
 
-void SystraceParser::ParseSdeTracingMarkWrite(int64_t ts,
-                                              uint32_t pid,
-                                              char trace_type,
-                                              bool trace_begin,
-                                              base::StringView trace_name,
-                                              uint32_t /* tgid */,
-                                              int64_t value) {
+void SystraceParser::ParseTracingMarkWrite(int64_t ts,
+                                           uint32_t pid,
+                                           char trace_type,
+                                           bool trace_begin,
+                                           base::StringView trace_name,
+                                           uint32_t /* tgid */,
+                                           int64_t value) {
   systrace_utils::SystraceTracePoint point{};
   point.name = trace_name;
 
@@ -158,8 +160,9 @@
       UniquePid upid =
           context_->process_tracker->GetOrCreateProcess(point.tgid);
 
-      TrackId track_id = context_->track_tracker->InternAndroidAsyncTrack(
-          name_id, upid, cookie);
+      auto track_set_id =
+          context_->async_track_set_tracker->InternAndroidSet(upid, name_id);
+
       if (point.phase == 'S') {
         // Historically, async slices on Android did not support nesting async
         // slices (i.e. you could not have a stack of async slices). If clients
@@ -177,10 +180,16 @@
         // issues. No other code should ever use this method.
         tables::SliceTable::Row row;
         row.ts = ts;
-        row.track_id = track_id;
+        row.track_id =
+            context_->async_track_set_tracker->Begin(track_set_id, cookie);
         row.name = name_id;
-        context_->slice_tracker->BeginLegacyUnnestable(row);
+        context_->slice_tracker->BeginLegacyUnnestable(
+            row, [this, cookie](ArgsTracker::BoundInserter* inserter) {
+              inserter->AddArg(cookie_id_, Variadic::Integer(cookie));
+            });
       } else {
+        TrackId track_id =
+            context_->async_track_set_tracker->End(track_set_id, cookie);
         context_->slice_tracker->End(ts, track_id);
       }
       break;
@@ -209,14 +218,24 @@
         context_->event_tracker->PushCounter(ts, point.value, track);
         return;
       }
-      // This is per upid on purpose. Some counters are pushed from arbitrary
-      // threads but are really per process.
-      UniquePid upid =
-          context_->process_tracker->GetOrCreateProcess(point.tgid);
+
       StringId name_id = context_->storage->InternString(point.name);
-      TrackId track =
-          context_->track_tracker->InternProcessCounterTrack(name_id, upid);
-      context_->event_tracker->PushCounter(ts, point.value, track);
+      TrackId track_id;
+      if (point.tgid == 0) {
+        // If tgid is 0 (likely because this is a kernel thread), we can do no
+        // better than using a thread track with the pid of the process.
+        UniqueTid utid = context_->process_tracker->GetOrCreateThread(pid);
+        track_id =
+            context_->track_tracker->InternThreadCounterTrack(name_id, utid);
+      } else {
+        // This is per upid on purpose. Some counters are pushed from arbitrary
+        // threads but are really per process.
+        UniquePid upid =
+            context_->process_tracker->GetOrCreateProcess(point.tgid);
+        track_id =
+            context_->track_tracker->InternProcessCounterTrack(name_id, upid);
+      }
+      context_->event_tracker->PushCounter(ts, point.value, track_id);
     }
   }
 }
diff --git a/src/trace_processor/importers/systrace/systrace_parser.h b/src/trace_processor/importers/systrace/systrace_parser.h
index ae5eec6..f069019 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_parser.h
@@ -155,7 +155,7 @@
       size_t name_index = 2 + tgid_length + 1;
       out->name = base::StringView(
           s + name_index, len - name_index - (s[len - 1] == '\n' ? 1 : 0));
-      if (out->name.size() == 0)
+      if (out->name.empty())
         return SystraceParseResult::kFailure;
       return SystraceParseResult::kSuccess;
     }
@@ -230,13 +230,13 @@
 
   void ParsePrintEvent(int64_t ts, uint32_t pid, base::StringView event);
 
-  void ParseSdeTracingMarkWrite(int64_t ts,
-                                uint32_t pid,
-                                char trace_type,
-                                bool trace_begin,
-                                base::StringView trace_name,
-                                uint32_t tgid,
-                                int64_t value);
+  void ParseTracingMarkWrite(int64_t ts,
+                             uint32_t pid,
+                             char trace_type,
+                             bool trace_begin,
+                             base::StringView trace_name,
+                             uint32_t tgid,
+                             int64_t value);
 
   void ParseZeroEvent(int64_t ts,
                       uint32_t pid,
@@ -254,6 +254,7 @@
   TraceProcessorContext* const context_;
   const StringId lmk_id_;
   const StringId screen_state_id_;
+  const StringId cookie_id_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/systrace/systrace_trace_parser.cc b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
index 0640857..9749c66 100644
--- a/src/trace_processor/importers/systrace/systrace_trace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_trace_parser.cc
@@ -97,6 +97,8 @@
         state_ = ParseState::kSystrace;
       } else if (base::StartsWith(buffer, "PROCESS DUMP")) {
         state_ = ParseState::kProcessDumpLong;
+      } else if (base::StartsWith(buffer, "CGROUP DUMP")) {
+        state_ = ParseState::kCgroupDump;
       } else if (base::Contains(buffer, R"(</script>)")) {
         state_ = ParseState::kHtmlBeforeSystrace;
       }
@@ -169,6 +171,11 @@
               utid, cmd_id, ThreadNamePriority::kOther);
         }
       }
+    } else if (state_ == ParseState::kCgroupDump) {
+      if (base::Contains(buffer, R"(</script>)")) {
+        state_ = ParseState::kHtmlBeforeSystrace;
+      }
+      // TODO(lalitm): see if it is important to parse this.
     }
     start_it = line_it + 1;
   }
diff --git a/src/trace_processor/importers/systrace/systrace_trace_parser.h b/src/trace_processor/importers/systrace/systrace_trace_parser.h
index 09c82bb..1db04ee 100644
--- a/src/trace_processor/importers/systrace/systrace_trace_parser.h
+++ b/src/trace_processor/importers/systrace/systrace_trace_parser.h
@@ -46,6 +46,7 @@
     kSystrace,
     kProcessDumpLong,
     kProcessDumpShort,
+    kCgroupDump,
     kEndOfSystrace,
   };
 
diff --git a/src/trace_processor/metrics/BUILD.gn b/src/trace_processor/metrics/BUILD.gn
index e82ff13..0017914 100644
--- a/src/trace_processor/metrics/BUILD.gn
+++ b/src/trace_processor/metrics/BUILD.gn
@@ -22,6 +22,7 @@
   "android/android_surfaceflinger.sql",
   "android/android_cpu_agg.sql",
   "android/android_cpu_raw_metrics_per_core.sql",
+  "android/android_gpu.sql",
   "android/android_mem.sql",
   "android/android_mem_unagg.sql",
   "android/android_ion.sql",
@@ -43,6 +44,7 @@
   "android/android_hwui_metric.sql",
   "android/java_heap_histogram.sql",
   "android/java_heap_stats.sql",
+  "android/power_drain_in_watts.sql",
   "android/power_profile_data.sql",
   "android/process_unagg_mem_view.sql",
   "android/process_mem.sql",
@@ -50,18 +52,29 @@
   "android/process_oom_score.sql",
   "android/mem_stats_priority_breakdown.sql",
   "android/span_view_stats.sql",
+  "android/android_sysui_cuj.sql",
   "android/process_counter_span_view.sql",
-  "android/counter_span_view.sql",
-  "android/unmapped_java_symbols.sql",
+  "android/global_counter_span_view.sql",
   "android/unsymbolized_frames.sql",
+  "chrome/actual_power_by_category.sql",
+  "chrome/actual_power_by_rail_mode.sql",
+  "chrome/chrome_event_metadata.sql",
   "chrome/chrome_processes.sql",
+  "chrome/chrome_thread_slice_with_cpu_time.sql",
+  "chrome/cpu_time_by_category.sql",
+  "chrome/cpu_time_by_rail_mode.sql",
+  "chrome/estimated_power_by_category.sql",
+  "chrome/estimated_power_by_rail_mode.sql",
+  "chrome/rail_modes.sql",
   "chrome/scroll_jank.sql",
   "chrome/scroll_jank_cause.sql",
   "chrome/scroll_jank_cause_blocking_task.sql",
   "chrome/scroll_jank_cause_blocking_touch_move.sql",
+  "chrome/scroll_jank_cause_get_bitmap.sql",
+  "chrome/scroll_jank_cause_queuing_delay.sql",
   "chrome/scroll_flow_event.sql",
   "chrome/scroll_flow_event_queuing_delay.sql",
-  "chrome/console_error_metric.sql",
+  "chrome/test_chrome_metric.sql",
   "webview/webview_power_usage.sql",
 ]
 
@@ -81,12 +94,52 @@
   public_configs = [ ":gen_config" ]
 }
 
+action("gen_cc_metrics_descriptor") {
+  descriptor_target = "../../../protos/perfetto/metrics:descriptor"
+  generated_header = "${target_gen_dir}/metrics.descriptor.h"
+
+  descriptor_file_path = get_label_info(descriptor_target, "target_gen_dir") +
+                         "/metrics.descriptor"
+
+  script = "../../../tools/gen_cc_proto_descriptor.py"
+  deps = [ descriptor_target ]
+  args = [
+    "--gen_dir",
+    rebase_path(root_gen_dir, root_build_dir),
+    "--cpp_out",
+    rebase_path(generated_header, root_build_dir),
+    rebase_path(descriptor_file_path, root_build_dir),
+  ]
+  inputs = [ descriptor_file_path ]
+  outputs = [ generated_header ]
+  public_configs = [ ":gen_config" ]
+}
+
+action("gen_cc_all_chrome_metrics_descriptor") {
+  descriptor_target = "../../../protos/perfetto/metrics/chrome:descriptor"
+  generated_header = "${target_gen_dir}/chrome/all_chrome_metrics.descriptor.h"
+
+  descriptor_file_path = get_label_info(descriptor_target, "target_gen_dir") +
+                         "/all_chrome_metrics.descriptor"
+
+  script = "../../../tools/gen_cc_proto_descriptor.py"
+  deps = [ descriptor_target ]
+  args = [
+    "--gen_dir",
+    rebase_path(root_gen_dir, root_build_dir),
+    "--cpp_out",
+    rebase_path(generated_header, root_build_dir),
+    rebase_path(descriptor_file_path, root_build_dir),
+  ]
+  inputs = [ descriptor_file_path ]
+  outputs = [ generated_header ]
+  public_configs = [ ":gen_config" ]
+}
+
 if (enable_perfetto_trace_processor_sqlite) {
   source_set("lib") {
     sources = [
-      "chrome/all_chrome_metrics.descriptor.h",
       "metrics.cc",
-      "metrics.descriptor.h",
       "metrics.h",
     ]
     deps = [
@@ -101,6 +154,8 @@
       "../sqlite",
     ]
     public_deps = [
+      ":gen_cc_all_chrome_metrics_descriptor",
+      ":gen_cc_metrics_descriptor",
       ":gen_merged_sql_metrics",
       "../util:descriptors",
     ]
diff --git a/src/trace_processor/metrics/android/android_batt.sql b/src/trace_processor/metrics/android/android_batt.sql
index b74e5f1..e76a6ae 100644
--- a/src/trace_processor/metrics/android/android_batt.sql
+++ b/src/trace_processor/metrics/android/android_batt.sql
@@ -52,57 +52,99 @@
 ) USING(ts)
 ORDER BY ts;
 
-DROP TABLE IF EXISTS android_batt_wakelocks_raw_;
-CREATE TABLE android_batt_wakelocks_raw_ AS
+DROP TABLE IF EXISTS android_batt_wakelocks_merged;
+CREATE TABLE android_batt_wakelocks_merged AS
 SELECT
-  ts,
-  dur,
-  ts+dur AS ts_end
-FROM slice
-WHERE slice.name LIKE 'WakeLock %' AND dur != -1;
+  MIN(ts) AS ts,
+  MAX(ts_end) AS ts_end
+FROM (
+    SELECT
+        *,
+        SUM(new_group) OVER (ORDER BY ts) AS group_id
+    FROM (
+        SELECT
+            ts,
+            ts + dur AS ts_end,
+            -- There is a new group if there was a gap before this wakelock.
+            -- i.e. the max end timestamp of all preceding wakelocks is before
+            -- the start timestamp of this one.
+            -- The null check is for the first row which is always a new group.
+            IFNULL(
+                MAX(ts + dur) OVER (
+                    ORDER BY ts
+                    ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
+                ) < ts,
+                true
+            ) AS new_group
+        FROM slice
+        WHERE slice.name LIKE 'WakeLock %' AND dur != -1
+    )
+)
+GROUP BY group_id;
 
-DROP TABLE IF EXISTS android_batt_wakelocks_labelled_;
-CREATE TABLE android_batt_wakelocks_labelled_ AS
-SELECT
-  *,
-  NOT EXISTS (
-    SELECT *
-    FROM android_batt_wakelocks_raw_ AS t2
-    WHERE t2.ts < t1.ts
-      AND t2.ts_end >= t1.ts
-  ) AS no_overlap_at_start,
-  NOT EXISTS (
-    SELECT *
-    FROM android_batt_wakelocks_raw_ AS t2
-    WHERE t2.ts_end > t1.ts_end
-      AND t2.ts <= t1.ts_end
-  ) AS no_overlap_at_end
-FROM android_batt_wakelocks_raw_ AS t1;
+-- Different device kernels log different actions when suspending. This table
+-- tells us the action that straddles the actual suspend period.
+CREATE TABLE device_action_mapping (device TEXT, action TEXT);
+INSERT INTO device_action_mapping VALUES
+('blueline', 'timekeeping_freeze'),
+('crosshatch', 'timekeeping_freeze'),
+('bonito', 'timekeeping_freeze'),
+('sargo', 'timekeeping_freeze'),
+('coral', 'timekeeping_freeze'),
+('flame', 'timekeeping_freeze'),
+('sunfish', 'timekeeping_freeze'),
+('redfin', 'syscore_resume'),
+('bramble', 'syscore_resume');
 
-CREATE VIEW android_batt_wakelocks_merged AS
-SELECT
-  ts,
-  (
-    SELECT min(ts_end)
-    FROM android_batt_wakelocks_labelled_ AS ends
-    WHERE no_overlap_at_end
-      AND ends.ts_end >= starts.ts
-  ) AS ts_end
-FROM android_batt_wakelocks_labelled_ AS starts
-WHERE no_overlap_at_start;
+CREATE TABLE device_action AS
+SELECT action
+FROM device_action_mapping dam
+WHERE EXISTS (
+  SELECT 1 FROM metadata
+  WHERE name = 'android_build_fingerprint' AND str_value LIKE '%' || dam.device || '%');
 
 CREATE TABLE suspend_slice_ AS
+-- Traces from after b/70292203 was fixed have the action string so just look
+-- for it.
 SELECT
-       ts,
-       lead_ts - ts AS dur
+    ts,
+    dur,
+    true as trustworthy
+FROM (
+    SELECT
+        ts,
+        LEAD(ts) OVER (ORDER BY ts, start DESC) - ts AS dur,
+        start
+    FROM (
+        SELECT
+               ts,
+               EXTRACT_ARG(arg_set_id, 'action') AS action,
+               EXTRACT_ARG(arg_set_id, 'start') AS start
+        FROM raw
+        WHERE name = 'suspend_resume'
+    ) JOIN device_action USING(action)
+)
+WHERE start = 1
+UNION ALL
+-- Traces from before b/70292203 was fixed (approx Nov 2020) do not have the
+-- action string so we do some convoluted pattern matching that mostly works.
+-- TODO(simonmacm) remove this when enough time has passed (mid 2021?)
+SELECT
+    ts,
+    dur,
+    false as trustworthy
 FROM (
     SELECT
        ts,
+       ts - lag(ts) OVER w AS lag_dur,
+       lead(ts) OVER w - ts AS dur,
+       action,
        start,
        event,
        lag(start) OVER w AS lag_start,
        lag(event) OVER w AS lag_event,
-       lead(ts) OVER w AS lead_ts,
+       lag(start, 2) OVER w AS lag_2_start,
+       lag(event, 2) OVER w AS lag_2_event,
        lead(start) OVER w AS lead_start,
        lead(event) OVER w AS lead_event,
        lead(start, 2) OVER w AS lead_2_start,
@@ -110,6 +152,7 @@
     FROM (
         SELECT
                ts,
+               EXTRACT_ARG(arg_set_id, 'action') AS action,
                EXTRACT_ARG(arg_set_id, 'start') AS start,
                EXTRACT_ARG(arg_set_id, 'val') AS event
         FROM raw
@@ -117,25 +160,48 @@
     )
     WINDOW w AS (ORDER BY ts)
 )
+WHERE action IS NULL AND (
 -- We want to find the start and end events with action='timekeeping_freeze'.
--- Unfortunately a bug leads to the action string being lost.
--- In practice, these events always show up in a sequence like the following:
--- start = 1, event = 1   [string would have been 'machine_suspend']
--- start = 1, event != 1  [string would have been 'timekeeping_freeze'] *
+-- In practice, these events often show up in a sequence like the following:
+-- start = 1, event = 1     [string would have been 'machine_suspend']
+-- start = 1, event = (any) [string would have been 'timekeeping_freeze'] *
 --
---                           (sleep happens here)
+--                             (sleep happens here)
 --
--- start = 0, event != 1  [string would have been 'timekeeping_freeze']
--- start = 0, event = 1   [string would have been 'machine_suspend']
+-- start = 0, event = (any) [string would have been 'timekeeping_freeze']
+-- start = 0, event = 1     [string would have been 'machine_suspend']
 --
 -- So we look for this pattern of start and event, anchored on the event marked
 -- with "*".
-WHERE start = 1 AND event != 1
-  AND lag_start = 1 AND lag_event = 1
-  AND lead_start = 0 AND lead_event != 1
-  AND lead_2_start = 0 AND lead_2_event = 1;
+    (
+        lag_start = 1 AND lag_event = 1
+        AND start = 1
+        AND lead_start = 0
+        AND lead_2_start = 0 AND lead_2_event = 1
+    )
+-- Or in newer kernels we seem to have a very different pattern. We can take
+-- advantage of that fact that we get several events with identical timestamp
+-- just before sleeping (normally this never happens):
+-- gap = 0, start = 1, event = 3
+-- gap = 0, start = 0, event = 3
+-- gap = 0, start = 1, event = 0
+--
+--  (sleep happens here)
+--
+-- gap = (any), start = 0, event = 0
+    OR (
+        lag_dur = 0
+        AND lead_start = 0 AND lead_event = 0
+        AND start = 1 AND event = 0
+        AND lag_start = 0 AND lag_event = 3
+        AND lag_2_start = 1 AND lag_2_event = 3
+    )
+);
 
-SELECT RUN_METRIC('android/counter_span_view.sql',
+DROP TABLE device_action_mapping;
+DROP TABLE device_action;
+
+SELECT RUN_METRIC('android/global_counter_span_view.sql',
   'table_name', 'screen_state',
   'counter_name', 'ScreenState');
 
@@ -191,5 +257,15 @@
          SELECT dur, screen_state_val AS state, 'sleep' AS tbl
          FROM screen_state_span_with_suspend
     )
+  ),
+  'suspend_period', (
+    SELECT RepeatedField(
+      AndroidBatteryMetric_SuspendPeriod(
+        'timestamp_ns', ts,
+        'duration_ns', dur
+      )
+    )
+    FROM suspend_slice_
+    WHERE trustworthy
   )
 );
diff --git a/src/trace_processor/metrics/android/android_cpu_agg.sql b/src/trace_processor/metrics/android/android_cpu_agg.sql
index 521f580..d5f145e 100644
--- a/src/trace_processor/metrics/android/android_cpu_agg.sql
+++ b/src/trace_processor/metrics/android/android_cpu_agg.sql
@@ -16,6 +16,7 @@
 
 -- Create all the views used to aggregate CPU data.
 -- View with start and end ts for each cpu frequency, per cpu.
+DROP VIEW IF EXISTS cpu_freq_view;
 CREATE VIEW cpu_freq_view AS
 SELECT
   cpu,
@@ -28,5 +29,6 @@
 WHERE name = 'cpufreq';
 
 -- View that joins the cpufreq table with the slice table.
+DROP TABLE IF EXISTS cpu_freq_sched_per_thread;
 CREATE VIRTUAL TABLE cpu_freq_sched_per_thread
 USING SPAN_LEFT_JOIN(sched PARTITIONED cpu, cpu_freq_view PARTITIONED cpu);
diff --git a/src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql b/src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql
index 01f60e5..06cee75 100644
--- a/src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql
+++ b/src/trace_processor/metrics/android/android_cpu_raw_metrics_per_core.sql
@@ -27,7 +27,7 @@
   -- We use millicycles as we want to preserve this level of precision
   -- for future calculations.
   SUM(dur * freq_khz / 1000) AS millicycles,
-  CAST(SUM(dur * freq_khz / 1000000 / 1000000) AS INT) AS mcycles,
+  CAST(SUM(dur * freq_khz / 1000) / 1e9 AS INT) AS mcycles,
   SUM(dur) AS runtime_ns,
   MIN(freq_khz) AS min_freq_khz,
   MAX(freq_khz) AS max_freq_khz,
diff --git a/src/trace_processor/metrics/android/android_gpu.sql b/src/trace_processor/metrics/android/android_gpu.sql
new file mode 100644
index 0000000..b1a0a04
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_gpu.sql
@@ -0,0 +1,63 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('android/global_counter_span_view.sql',
+  'table_name', 'global_gpu_memory',
+  'counter_name', 'GPU Memory');
+
+SELECT RUN_METRIC('android/process_counter_span_view.sql',
+  'table_name', 'proc_gpu_memory',
+  'counter_name', 'GPU Memory');
+
+CREATE VIEW proc_gpu_memory_view AS
+SELECT
+  upid,
+  MAX(proc_gpu_memory_val) as mem_max,
+  MIN(proc_gpu_memory_val) as mem_min,
+  SUM(proc_gpu_memory_val * dur) as mem_valxdur,
+  SUM(dur) as mem_dur
+FROM proc_gpu_memory_span
+GROUP BY upid;
+
+CREATE VIEW agg_proc_gpu_view AS
+SELECT
+  name,
+  MAX(mem_max) as mem_max,
+  MIN(mem_min) as mem_min,
+  SUM(mem_valxdur) / SUM(mem_dur) as mem_avg
+FROM process
+JOIN proc_gpu_memory_view
+USING(upid)
+GROUP BY name;
+
+CREATE VIEW proc_gpu_view AS
+SELECT
+  AndroidGpuMetric_Process(
+    'name', name,
+    'mem_max', CAST(mem_max as INT64),
+    'mem_min', CAST(mem_min as INT64),
+    'mem_avg', CAST(mem_avg as INT64)
+  ) AS proto
+FROM agg_proc_gpu_view;
+
+CREATE VIEW android_gpu_output AS
+SELECT AndroidGpuMetric(
+  'processes', (SELECT RepeatedField(proto) FROM proc_gpu_view),
+  'mem_max', CAST(MAX(global_gpu_memory_val) as INT64),
+  'mem_min', CAST(MIN(global_gpu_memory_val) as INT64),
+  'mem_avg', CAST(SUM(global_gpu_memory_val * dur) / SUM(dur) as INT64)
+)
+FROM global_gpu_memory_span;
diff --git a/src/trace_processor/metrics/android/android_proxy_power.sql b/src/trace_processor/metrics/android/android_proxy_power.sql
index 36711e3..c356edd 100644
--- a/src/trace_processor/metrics/android/android_proxy_power.sql
+++ b/src/trace_processor/metrics/android/android_proxy_power.sql
@@ -42,6 +42,7 @@
 SELECT RUN_METRIC('android/android_cpu_agg.sql');
 SELECT RUN_METRIC('android/power_profile_data.sql');
 
+DROP VIEW IF EXISTS device;
 CREATE VIEW device AS
 WITH
   after_first_slash(str) AS (
@@ -55,6 +56,7 @@
   )
 SELECT str AS name FROM before_second_slash;
 
+DROP VIEW IF EXISTS power_view;
 CREATE VIEW power_view AS
 SELECT
   cpu_freq_view.cpu AS cpu,
@@ -68,5 +70,14 @@
   AND power_profile.freq = cpu_freq_view.freq_khz
 );
 
+-- utid = 0 is a reserved value used to mark sched slices where CPU was idle.
+-- It doesn't correspond to any real thread.
+DROP VIEW IF EXISTS sched_real_threads;
+CREATE VIEW sched_real_threads AS
+SELECT *
+FROM sched
+WHERE utid != 0;
+
+DROP TABLE IF EXISTS power_per_thread;
 CREATE VIRTUAL TABLE power_per_thread
-USING SPAN_LEFT_JOIN(sched PARTITIONED cpu, power_view PARTITIONED cpu);
+USING SPAN_LEFT_JOIN(sched_real_threads PARTITIONED cpu, power_view PARTITIONED cpu);
diff --git a/src/trace_processor/metrics/android/android_startup.sql b/src/trace_processor/metrics/android/android_startup.sql
index 4ac15cb..417397d 100644
--- a/src/trace_processor/metrics/android/android_startup.sql
+++ b/src/trace_processor/metrics/android/android_startup.sql
@@ -82,6 +82,7 @@
   'ActivityThreadMain',
   'bindApplication',
   'activityStart',
+  'activityRestart',
   'activityResume',
   'Choreographer#doFrame',
   'inflate')
@@ -199,6 +200,10 @@
         SELECT slice_proto FROM main_process_slice
         WHERE launch_id = launches.id AND name = 'activityResume'
       ),
+      'time_activity_restart', (
+        SELECT slice_proto FROM main_process_slice
+        WHERE launch_id = launches.id AND name = 'activityRestart'
+      ),
       'time_choreographer', (
         SELECT slice_proto FROM main_process_slice
         WHERE launch_id = launches.id AND name = 'Choreographer#doFrame'
diff --git a/src/trace_processor/metrics/android/android_startup_launches.sql b/src/trace_processor/metrics/android/android_startup_launches.sql
index 1fcb059..4ead55f 100644
--- a/src/trace_processor/metrics/android/android_startup_launches.sql
+++ b/src/trace_processor/metrics/android/android_startup_launches.sql
@@ -85,8 +85,11 @@
 JOIN launching_events ON
   (launching_events.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur) AND
   (launching_events.ts_end BETWEEN lpart.ts AND lpart.ts + lpart.dur)
-JOIN activity_intent_launch_successful AS successful
-  ON successful.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur;
+WHERE (
+  SELECT COUNT(1)
+  FROM activity_intent_launch_successful AS successful
+  WHERE successful.ts BETWEEN lpart.ts AND lpart.ts + lpart.dur
+) > 0;
 
 -- Maps a launch to the corresponding set of processes that handled the
 -- activity start. The vast majority of cases should be a single process.
diff --git a/src/trace_processor/metrics/android/android_sysui_cuj.sql b/src/trace_processor/metrics/android/android_sysui_cuj.sql
new file mode 100644
index 0000000..ec368d0
--- /dev/null
+++ b/src/trace_processor/metrics/android/android_sysui_cuj.sql
@@ -0,0 +1,323 @@
+--
+-- Copyright 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
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_last_cuj AS
+  SELECT
+    process.name AS process_name,
+    process.upid AS upid,
+    main_thread.utid AS main_thread_utid,
+    main_thread.name AS main_thread_name,
+    thread_track.id AS main_thread_track_id,
+    slice.name AS slice_name,
+    ts AS ts_start,
+    ts + dur AS ts_end
+  FROM slice
+  JOIN process_track ON slice.track_id = process_track.id
+  JOIN process USING (upid)
+  JOIN thread AS main_thread ON main_thread.upid = process.upid AND main_thread.is_main_thread
+  JOIN thread_track USING (utid)
+  WHERE
+    slice.name LIKE 'Cuj<%>'
+    AND slice.dur > 0
+    AND process.name IN (
+      'com.android.systemui',
+      'com.google.android.apps.nexuslauncher')
+  ORDER BY ts desc
+  LIMIT 1;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_render_thread AS
+  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
+  FROM thread
+  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
+  WHERE thread.name = 'RenderThread';
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_gpu_completion_thread AS
+  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
+  FROM thread
+  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
+  WHERE thread.name = 'GPU completion';
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_hwc_release_thread AS
+  SELECT thread.*, last_cuj.ts_start as ts_cuj_start, last_cuj.ts_end as ts_cuj_end
+  FROM thread
+  JOIN android_sysui_cuj_last_cuj last_cuj USING (upid)
+  WHERE thread.name = 'HWC release';
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_main_thread_slices AS
+  SELECT slice.*, ts + dur AS ts_end
+  FROM slice
+  JOIN android_sysui_cuj_last_cuj last_cuj
+    ON slice.track_id = last_cuj.main_thread_track_id
+  WHERE ts >= last_cuj.ts_start AND ts <= last_cuj.ts_end;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_render_thread_slices AS
+  SELECT slice.*, ts + dur AS ts_end
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN android_sysui_cuj_render_thread USING (utid)
+  WHERE ts >= ts_cuj_start AND ts <= ts_cuj_end;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_gpu_completion_slices AS
+  SELECT
+    slice.*,
+    ts + dur AS ts_end,
+    -- Extracts 1234 from 'waiting for GPU completion 1234'
+    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN android_sysui_cuj_gpu_completion_thread USING (utid)
+  WHERE
+    slice.name LIKE 'waiting for GPU completion %'
+    AND ts >= ts_cuj_start AND ts <= ts_cuj_end;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_hwc_release_slices AS
+  SELECT
+    slice.*,
+    ts + dur as ts_end,
+    -- Extracts 1234 from 'waiting for HWC release 1234'
+    CAST(STR_SPLIT(slice.name, ' ', 4) AS INTEGER) as idx
+  FROM slice
+  JOIN thread_track ON slice.track_id = thread_track.id
+  JOIN android_sysui_cuj_hwc_release_thread USING (utid)
+  WHERE
+    slice.name LIKE 'waiting for HWC release %'
+    AND ts >= ts_cuj_start AND ts <= ts_cuj_end;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_frames AS
+  WITH gcs_to_rt_match AS (
+    -- Match GPU Completion with the last RT slice before it
+    SELECT
+      gcs.ts as gcs_ts,
+      gcs.ts_end as gcs_ts_end,
+      gcs.dur as gcs_dur,
+      gcs.idx as idx,
+      MAX(rts.ts) as rts_ts
+    FROM android_sysui_cuj_gpu_completion_slices gcs
+    JOIN android_sysui_cuj_render_thread_slices rts ON rts.ts < gcs.ts
+    -- dispatchFrameCallbacks might be seen in case of
+    -- drawing that happens on RT only (e.g. ripple effect)
+    WHERE (rts.name = 'DrawFrame' OR rts.name = 'dispatchFrameCallbacks')
+    GROUP BY gcs.ts, gcs.ts_end, gcs.dur, gcs.idx
+  ),
+  frame_boundaries AS (
+    -- Match main thread doFrame with RT DrawFrame and optional GPU Completion
+    SELECT
+      mts.ts as mts_ts,
+      mts.ts_end as mts_ts_end,
+      mts.dur as mts_dur,
+      MAX(gcs_rt.gcs_ts) as gcs_ts_start,
+      MAX(gcs_rt.gcs_ts_end) as gcs_ts_end
+    FROM android_sysui_cuj_main_thread_slices mts
+    JOIN android_sysui_cuj_render_thread_slices rts
+      ON mts.ts < rts.ts AND mts.ts_end >= rts.ts
+    LEFT JOIN gcs_to_rt_match gcs_rt ON gcs_rt.rts_ts = rts.ts
+    WHERE mts.name = 'Choreographer#doFrame' AND rts.name = 'DrawFrame'
+    GROUP BY mts.ts, mts.ts_end, mts.dur
+  )
+  SELECT
+    ROW_NUMBER() OVER (ORDER BY f.mts_ts) AS frame_number,
+    f.mts_ts as ts_main_thread_start,
+    f.mts_ts_end as ts_main_thread_end,
+    f.mts_dur AS dur_main_thread,
+    MIN(rts.ts) AS ts_render_thread_start,
+    MAX(rts.ts_end) AS ts_render_thread_end,
+    SUM(rts.dur) AS dur_render_thread,
+    MAX(gcs_rt.gcs_ts_end) AS ts_frame_end,
+    MAX(gcs_rt.gcs_ts_end) - f.mts_ts AS dur_frame,
+    SUM(gcs_rt.gcs_ts_end - MAX(COALESCE(hwc.ts_end, 0), gcs_rt.gcs_ts)) as dur_gcs,
+    COUNT(DISTINCT(rts.ts)) as draw_frames,
+    COUNT(DISTINCT(gcs_rt.gcs_ts)) as gpu_completions
+  FROM frame_boundaries f
+  JOIN android_sysui_cuj_render_thread_slices rts
+    ON f.mts_ts < rts.ts AND f.mts_ts_end >= rts.ts
+  LEFT JOIN gcs_to_rt_match gcs_rt
+    ON rts.ts = gcs_rt.rts_ts
+  LEFT JOIN android_sysui_cuj_hwc_release_slices hwc USING (idx)
+  WHERE rts.name = 'DrawFrame'
+  GROUP BY f.mts_ts
+  HAVING gpu_completions >= 1;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_frame_main_thread_bounds AS
+SELECT frame_number, ts_main_thread_start as ts, dur_main_thread as dur
+FROM android_sysui_cuj_frames;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_main_thread_state_data AS
+SELECT * FROM thread_state
+WHERE utid = (SELECT main_thread_utid FROM android_sysui_cuj_last_cuj);
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_sysui_cuj_main_thread_state_vt
+USING span_left_join(android_sysui_cuj_frame_main_thread_bounds, android_sysui_cuj_main_thread_state_data PARTITIONED utid);
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_main_thread_state AS
+  SELECT
+    frame_number,
+    state,
+    io_wait AS io_wait,
+    SUM(dur) AS dur
+  FROM android_sysui_cuj_main_thread_state_vt
+  GROUP BY frame_number, state, io_wait
+  HAVING dur > 0;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_frame_render_thread_bounds AS
+SELECT frame_number, ts_render_thread_start as ts, dur_render_thread as dur
+FROM android_sysui_cuj_frames;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_render_thread_state_data AS
+SELECT * FROM thread_state
+WHERE utid in (SELECT utid FROM android_sysui_cuj_render_thread);
+
+CREATE VIRTUAL TABLE IF NOT EXISTS android_sysui_cuj_render_thread_state_vt
+USING span_left_join(android_sysui_cuj_frame_render_thread_bounds, android_sysui_cuj_render_thread_state_data PARTITIONED utid);
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_render_thread_state AS
+  SELECT
+    frame_number,
+    state,
+    io_wait AS io_wait,
+    SUM(dur) AS dur
+  FROM android_sysui_cuj_render_thread_state_vt
+  GROUP BY frame_number, state, io_wait
+  HAVING dur > 0;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_main_thread_binder AS
+  SELECT
+    f.frame_number,
+    SUM(mts.dur) AS dur,
+    COUNT(*) AS call_count
+  FROM android_sysui_cuj_frames f
+  JOIN android_sysui_cuj_main_thread_slices mts
+    ON mts.ts >= f.ts_main_thread_start AND mts.ts < f.ts_main_thread_end
+  WHERE mts.name = 'binder transaction'
+  GROUP BY f.frame_number;
+
+CREATE TABLE IF NOT EXISTS android_sysui_cuj_jank_causes AS
+  SELECT
+  frame_number,
+  'RenderThread - long shader_compile' AS jank_cause
+  FROM android_sysui_cuj_frames f
+  JOIN android_sysui_cuj_render_thread_slices rts
+    ON rts.ts >= f.ts_render_thread_start AND rts.ts < f.ts_render_thread_end
+  WHERE rts.name = 'shader_compile'
+  AND rts.dur > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'RenderThread - long flush layers' AS jank_cause
+  FROM android_sysui_cuj_frames f
+  JOIN android_sysui_cuj_render_thread_slices rts
+    ON rts.ts >= f.ts_render_thread_start AND rts.ts < f.ts_render_thread_end
+  WHERE rts.name = 'flush layers'
+  AND rts.dur > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'MainThread - IO wait time' AS jank_cause
+  FROM android_sysui_cuj_main_thread_state
+  WHERE
+    ((state = 'D' OR state = 'DK') AND io_wait)
+    OR (state = 'DK' AND io_wait IS NULL)
+  GROUP BY frame_number
+  HAVING SUM(dur) > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'MainThread - scheduler' AS jank_cause
+  FROM android_sysui_cuj_main_thread_state
+  WHERE (state = 'R' OR state = 'R+')
+  GROUP BY frame_number
+  HAVING SUM(dur) > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'RenderThread - IO wait time' AS jank_cause
+  FROM android_sysui_cuj_render_thread_state
+  WHERE
+    ((state = 'D' OR state = 'DK') AND io_wait)
+    OR (state = 'DK' AND io_wait IS NULL)
+  GROUP BY frame_number
+  HAVING SUM(dur) > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'RenderThread - scheduler' AS jank_cause
+  FROM android_sysui_cuj_render_thread_state
+  WHERE (state = 'R' OR state = 'R+')
+  GROUP BY frame_number
+  HAVING SUM(dur) > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'MainThread - binder transaction time' AS jank_cause
+  FROM android_sysui_cuj_main_thread_binder
+  WHERE dur > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'MainThread - binder calls count' AS jank_cause
+  FROM android_sysui_cuj_main_thread_binder
+  WHERE call_count > 8
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'GPU completion - long completion time' AS jank_cause
+  FROM android_sysui_cuj_frames f
+  WHERE dur_gcs > 8000000
+
+  UNION ALL
+  SELECT
+  frame_number,
+  'Long running time' as jank_cause
+  FROM android_sysui_cuj_main_thread_state mts
+  JOIN android_sysui_cuj_render_thread_state rts USING(frame_number)
+  WHERE
+    mts.state = 'Running'
+    AND rts.state = 'Running'
+    AND mts.dur + rts.dur > 15000000;
+
+-- TODO(b/175098682): Switch to use async slices
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_event AS
+ SELECT
+    'slice' as track_type,
+    (SELECT slice_name FROM android_sysui_cuj_last_cuj)
+        || ' - jank cause' as track_name,
+    f.ts_main_thread_start as ts,
+    f.dur_main_thread as dur,
+    group_concat(jc.jank_cause) as slice_name
+FROM android_sysui_cuj_frames f
+JOIN android_sysui_cuj_jank_causes jc USING (frame_number)
+GROUP BY track_type, track_name, ts, dur;
+
+CREATE VIEW IF NOT EXISTS android_sysui_cuj_output AS
+SELECT
+  AndroidSysUiCujMetrics(
+      'frames',
+       (SELECT RepeatedField(
+         AndroidSysUiCujMetrics_Frame(
+           'number', f.frame_number,
+           'ts', f.ts_main_thread_start,
+           'dur', f.dur_frame,
+           'jank_cause',
+              (SELECT RepeatedField(jc.jank_cause)
+              FROM android_sysui_cuj_jank_causes jc WHERE jc.frame_number = f.frame_number)))
+       FROM android_sysui_cuj_frames f
+       ORDER BY frame_number ASC));
diff --git a/src/trace_processor/metrics/android/android_thread_time_in_state.sql b/src/trace_processor/metrics/android/android_thread_time_in_state.sql
index e6da0f4..bb7277d 100644
--- a/src/trace_processor/metrics/android/android_thread_time_in_state.sql
+++ b/src/trace_processor/metrics/android/android_thread_time_in_state.sql
@@ -20,7 +20,7 @@
 CREATE TABLE IF NOT EXISTS android_thread_time_in_state_base AS
 SELECT
   base.*,
-  IFNULL(core_type_per_cpu.core_type, 'unknown') core_type
+  core_type_per_cpu.core_type core_type
 FROM (
   SELECT
     ts,
@@ -32,7 +32,7 @@
   JOIN thread_counter_track ON (counter.track_id = thread_counter_track.id)
   WHERE thread_counter_track.name = 'time_in_state'
 ) base
-LEFT JOIN core_type_per_cpu USING (cpu);
+JOIN core_type_per_cpu USING (cpu);
 
 CREATE VIEW android_thread_time_in_state_raw AS
 SELECT
diff --git a/src/trace_processor/metrics/android/display_metrics.sql b/src/trace_processor/metrics/android/display_metrics.sql
index 321ef4d..4205c1f 100644
--- a/src/trace_processor/metrics/android/display_metrics.sql
+++ b/src/trace_processor/metrics/android/display_metrics.sql
@@ -24,10 +24,18 @@
 FROM counters
 WHERE name='SAME_FRAME' AND value=0;
 
+CREATE VIEW dpu_underrun AS
+SELECT COUNT(name) AS total_dpu_underrun_count
+FROM counters
+WHERE name='DPU_UNDERRUN'
+AND value=1;
+
 CREATE VIEW display_metrics_output AS
 SELECT AndroidDisplayMetrics(
     'total_duplicate_frames', (SELECT total_duplicate_frames
                             FROM same_frame),
     'duplicate_frames_logged', (SELECT logs_found
-                            FROM duplicate_frames_logged)
-);
\ No newline at end of file
+                            FROM duplicate_frames_logged),
+    'total_dpu_underrun_count', (SELECT total_dpu_underrun_count
+                            FROM dpu_underrun)
+);
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/src/trace_processor/metrics/android/global_counter_span_view.sql
similarity index 93%
rename from src/trace_processor/metrics/android/counter_span_view.sql
rename to src/trace_processor/metrics/android/global_counter_span_view.sql
index cdd953a..2d3364c 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/src/trace_processor/metrics/android/global_counter_span_view.sql
@@ -22,4 +22,5 @@
   value AS {{table_name}}_val
 FROM counter c JOIN counter_track t
   ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+WHERE t.type = 'counter_track'
+  AND name = '{{counter_name}}';
diff --git a/src/trace_processor/metrics/android/heap_profile_callsites.sql b/src/trace_processor/metrics/android/heap_profile_callsites.sql
index 373c8dc..27bebd3 100644
--- a/src/trace_processor/metrics/android/heap_profile_callsites.sql
+++ b/src/trace_processor/metrics/android/heap_profile_callsites.sql
@@ -42,10 +42,11 @@
 FROM (
   SELECT
     spf.id AS frame_id,
-    IFNULL(
+    COALESCE(
       (SELECT name FROM stack_profile_symbol symbol
         WHERE symbol.symbol_set_id = spf.symbol_set_id
         LIMIT 1),
+      spf.deobfuscated_name,
       spf.name
     ) AS symbol_name,
     spm.name AS mapping_name
diff --git a/src/trace_processor/metrics/android/hsc_startups.sql b/src/trace_processor/metrics/android/hsc_startups.sql
index 45b3d70..974d919 100644
--- a/src/trace_processor/metrics/android/hsc_startups.sql
+++ b/src/trace_processor/metrics/android/hsc_startups.sql
@@ -15,28 +15,40 @@
 --
 
 -- Must be invoked after populating launches table in android_startup.
-CREATE VIEW frame_times AS
-SELECT
-    slices.ts AS ts,
-    slices.ts + slices.dur AS ts_end,
-    launches.package AS name,
-    launches.id AS launch_id,
-    ROW_NUMBER() OVER(PARTITION BY launches.id ORDER BY slices.ts ASC) as frame_number
-FROM slices
-INNER JOIN thread_track on slices.track_id = thread_track.id
-INNER JOIN thread USING(utid)
-INNER JOIN launches on launches.package LIKE '%' || thread.name || '%'
-WHERE slices.name="Choreographer#doFrame" and slices.ts > launches.ts;
-
 CREATE VIEW functions AS
 SELECT
     slices.ts as ts,
     slices.dur as dur,
-    thread.name as process_name,
+    process.name as process_name,
+    thread.name as thread_name,
     slices.name as function_name
 FROM slices
+INNER JOIN thread_track on slices.track_id = thread_track.id
+INNER JOIN thread USING(utid)
+INNER JOIN process USING(upid);
+
+-- Animators don't occur on threads, so add them here.
+CREATE VIEW animators AS
+SELECT
+    slices.ts AS ts,
+    slices.dur AS dur,
+    thread.name AS process_name,
+    slices.name AS animator_name
+FROM slices
 INNER JOIN process_track on slices.track_id = process_track.id
-INNER JOIN thread USING(upid);
+INNER JOIN thread USING(upid)
+WHERE slices.name LIKE "animator%";
+
+CREATE VIEW frame_times AS
+SELECT
+    functions.ts AS ts,
+    functions.ts + functions.dur AS ts_end,
+    launches.package AS name,
+    launches.id AS launch_id,
+    ROW_NUMBER() OVER(PARTITION BY launches.id ORDER BY functions.ts ASC) as number
+FROM functions
+INNER JOIN launches on launches.package LIKE '%' || functions.process_name || '%'
+WHERE functions.function_name="Choreographer#doFrame" AND functions.ts > launches.ts;
 
 CREATE TABLE hsc_based_startup_times(package STRING, id INT, ts_total INT);
 
@@ -48,7 +60,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%roid.calcul%" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=2 AND frame_times.name LIKE "%roid.calcul%" AND frame_times.launch_id = launches.id;
 
 -- Calendar
 INSERT INTO hsc_based_startup_times
@@ -58,8 +70,8 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name LIKE "animator:growScale" AND process_name LIKE "%id.calendar" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.calendar%" AND frame_times.launch_id = launches.id
-ORDER BY ts_total LIMIT 1;
+WHERE frame_times.name LIKE "%id.calendar%" AND frame_times.launch_id = launches.id
+ORDER BY ABS(frame_times.ts_end - (SELECT ts + dur FROM functions WHERE function_name LIKE "DrawFrame" AND process_name LIKE "%id.calendar" ORDER BY ts LIMIT 1)) LIMIT 1;
 
 -- Camera
 INSERT INTO hsc_based_startup_times
@@ -69,7 +81,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%GoogleCamera%" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=2 AND frame_times.name LIKE "%GoogleCamera%" AND frame_times.launch_id = launches.id;
 
 -- Chrome
 INSERT INTO hsc_based_startup_times
@@ -79,7 +91,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%chrome%" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=1 AND frame_times.name LIKE "%chrome%" AND frame_times.launch_id = launches.id;
 
 -- Clock
 INSERT INTO hsc_based_startup_times
@@ -89,7 +101,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%id.deskclock" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.deskclock" AND frame_times.launch_id = launches.id
+WHERE frame_times.ts > (SELECT ts + dur FROM animators WHERE animator_name="animator:translationZ" AND process_name LIKE "%id.deskclock" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.deskclock" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Contacts
@@ -100,8 +112,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%id.contacts" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%id.contacts" AND frame_times.launch_id = launches.id
-ORDER BY ts_total LIMIT 1;
+WHERE frame_times.number=3 AND frame_times.name LIKE "%id.contacts" AND frame_times.launch_id=launches.id;
 
 -- Dialer
 INSERT INTO hsc_based_startup_times
@@ -111,7 +122,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=2 AND frame_times.name LIKE "%id.dialer" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=1 AND frame_times.name LIKE "%id.dialer" AND frame_times.launch_id=launches.id;
 
 -- Facebook
 INSERT INTO hsc_based_startup_times
@@ -121,7 +132,19 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=3 AND frame_times.name LIKE "%ok.katana" AND frame_times.launch_id = launches.id;
+WHERE frame_times.ts > (SELECT ts+dur FROM slices WHERE slices.name LIKE "fb_startup_complete" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%ok.katana" AND frame_times.launch_id = launches.id
+ORDER BY ts_total LIMIT 1;
+
+-- Facebook Messenger
+INSERT INTO hsc_based_startup_times
+SELECT
+    launches.package as package,
+    launches.id as id,
+    frame_times.ts_end - launches.ts as ts_total
+FROM frame_times
+INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
+WHERE frame_times.ts > (SELECT ts+dur FROM slices WHERE slices.name LIKE "msgr_cold_start_to_cached_content" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%book.orca" AND frame_times.launch_id = launches.id
+ORDER BY ts_total LIMIT 1;
 
 -- Gmail
 INSERT INTO hsc_based_startup_times
@@ -131,17 +154,19 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts > (SELECT ts + dur FROM functions WHERE function_name="animator:elevation" AND process_name LIKE "%android.gm" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%android.gm" AND frame_times.launch_id = launches.id
+WHERE frame_times.ts > (SELECT ts + dur FROM animators WHERE animator_name="animator:elevation" AND process_name LIKE "%android.gm" ORDER BY ts DESC LIMIT 1) AND frame_times.name LIKE "%android.gm" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Instagram
 INSERT INTO hsc_based_startup_times
 SELECT
-    package as package,
-    id as id,
-    (SELECT ts + dur FROM slices WHERE slices.name LIKE "Start proc%mqtt" ORDER BY ts LIMIT 1) - launches.ts as ts_total
-FROM launches
-WHERE launches.package="com.instagram.android";
+    launches.package as package,
+    launches.id as id,
+    frame_times.ts_end - launches.ts as ts_total
+FROM frame_times
+INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
+WHERE frame_times.ts > (SELECT ts+dur FROM slices WHERE slices.name LIKE "ig_cold_start_to_cached_content" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%gram.android" AND frame_times.launch_id = launches.id
+ORDER BY ts_total LIMIT 1;
 
 -- Maps
 INSERT INTO hsc_based_startup_times
@@ -151,7 +176,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%maps%" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=1 AND frame_times.name LIKE "%maps%" AND frame_times.launch_id = launches.id;
 
 -- Messages
 INSERT INTO hsc_based_startup_times
@@ -161,7 +186,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT ts + dur FROM functions WHERE function_name="animator:translationZ" AND process_name LIKE "%apps.messaging%" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%apps.messaging%" AND frame_times.launch_id = launches.id
+WHERE frame_times.ts_end > (SELECT ts + dur FROM animators WHERE animator_name="animator:translationZ" AND process_name LIKE "%apps.messaging%" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%apps.messaging%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Netflix
@@ -172,7 +197,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts < (SELECT ts FROM functions WHERE function_name LIKE "animator%" AND process_name LIKE "%lix.mediaclient" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%lix.mediaclient%" AND frame_times.launch_id = launches.id
+WHERE frame_times.ts < (SELECT ts FROM animators WHERE animator_name LIKE "animator%" AND process_name LIKE "%lix.mediaclient" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%lix.mediaclient%" AND frame_times.launch_id = launches.id
 ORDER BY ts_total DESC LIMIT 1;
 
 -- Photos
@@ -183,17 +208,9 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%apps.photos%" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=1 AND frame_times.name LIKE "%apps.photos%" AND frame_times.launch_id = launches.id;
 
--- Settings
-INSERT INTO hsc_based_startup_times
-SELECT
-    launches.package as package,
-    launches.id as id,
-    frame_times.ts_end - launches.ts as ts_total
-FROM frame_times
-INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=4 AND frame_times.name LIKE "%settings%" AND frame_times.launch_id = launches.id;
+-- Settings was deprecated in favor of reportFullyDrawn b/169694037.
 
 -- Snapchat
 INSERT INTO hsc_based_startup_times
@@ -203,7 +220,7 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%napchat.android" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=1 AND frame_times.name LIKE "%napchat.android" AND frame_times.launch_id = launches.id;
 
 -- Twitter
 INSERT INTO hsc_based_startup_times
@@ -213,7 +230,18 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.ts_end > (SELECT ts FROM functions WHERE function_name="animator" AND process_name LIKE "%tter.android" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%tter.android" AND frame_times.launch_id = launches.id
+WHERE frame_times.ts_end > (SELECT ts FROM animators WHERE animator_name="animator" AND process_name LIKE "%tter.android" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%tter.android" AND frame_times.launch_id = launches.id
+ORDER BY ts_total LIMIT 1;
+
+-- WhatsApp
+INSERT INTO hsc_based_startup_times
+SELECT
+    launches.package as package,
+    launches.id as id,
+    frame_times.ts_end - launches.ts as ts_total
+FROM frame_times
+INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
+WHERE frame_times.ts > (SELECT ts+dur FROM slices WHERE slices.name LIKE "wa_startup_complete" ORDER BY ts LIMIT 1) AND frame_times.name LIKE "%om.whatsapp" AND frame_times.launch_id = launches.id
 ORDER BY ts_total LIMIT 1;
 
 -- Youtube
@@ -224,4 +252,4 @@
     frame_times.ts_end - launches.ts as ts_total
 FROM frame_times
 INNER JOIN launches on launches.package LIKE '%' || frame_times.name || '%'
-WHERE frame_times.frame_number=1 AND frame_times.name LIKE "%id.youtube" AND frame_times.launch_id = launches.id;
+WHERE frame_times.number=2 AND frame_times.name LIKE "%id.youtube" AND frame_times.launch_id = launches.id;
diff --git a/src/trace_processor/metrics/android/java_heap_histogram.sql b/src/trace_processor/metrics/android/java_heap_histogram.sql
index a8d9b5f..bc81600 100644
--- a/src/trace_processor/metrics/android/java_heap_histogram.sql
+++ b/src/trace_processor/metrics/android/java_heap_histogram.sql
@@ -16,6 +16,29 @@
 
 SELECT RUN_METRIC('android/process_metadata.sql');
 
+CREATE TABLE IF NOT EXISTS android_special_classes AS
+WITH RECURSIVE cls_visitor(cls_id, category) AS (
+  SELECT id, name FROM heap_graph_class WHERE name IN (
+    'android.view.View',
+    'android.app.Activity',
+    'android.app.Fragment',
+    'android.app.Service',
+    'android.content.ContentProvider',
+    'android.content.BroadcastReceiver',
+    'android.content.Context',
+    'android.content.Intent',
+    'android.content.res.ApkAssets',
+    'android.os.Handler',
+    'android.os.Parcel',
+    'android.graphics.Bitmap',
+    'android.graphics.BaseCanvas',
+    'com.android.server.am.PendingIntentRecord')
+  UNION ALL
+  SELECT child.id, parent.category
+  FROM heap_graph_class child JOIN cls_visitor parent ON parent.cls_id = child.superclass_id
+)
+SELECT * FROM cls_visitor;
+
 CREATE VIEW IF NOT EXISTS java_heap_histogram_output AS
 WITH
 -- Base histogram table
@@ -24,10 +47,14 @@
     o.upid,
     o.graph_sample_ts,
     IFNULL(c.deobfuscated_name, c.name) AS type_name,
+    special.category,
     COUNT(1) obj_count,
     SUM(CASE o.reachable WHEN TRUE THEN 1 ELSE 0 END) reachable_obj_count
-  FROM heap_graph_object o JOIN heap_graph_class c ON o.type_id = c.id
-  GROUP BY 1, 2, 3
+  FROM heap_graph_object o
+  JOIN heap_graph_class c ON o.type_id = c.id
+  LEFT JOIN android_special_classes special ON special.cls_id = c.id
+  GROUP BY 1, 2, 3, 4
+  ORDER BY 6 DESC
 ),
 -- Group by to build the repeated field by upid, ts
 heap_obj_histogram_count_protos AS (
@@ -36,6 +63,7 @@
     graph_sample_ts,
     RepeatedField(JavaHeapHistogram_TypeCount(
       'type_name', type_name,
+      'category', category,
       'obj_count', obj_count,
       'reachable_obj_count', reachable_obj_count
     )) AS count_protos
diff --git a/src/trace_processor/metrics/android/power_drain_in_watts.sql b/src/trace_processor/metrics/android/power_drain_in_watts.sql
new file mode 100644
index 0000000..76bd98d
--- /dev/null
+++ b/src/trace_processor/metrics/android/power_drain_in_watts.sql
@@ -0,0 +1,73 @@
+--
+-- Copyright 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
+--
+--     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.
+--
+
+-- This is a mapping from counter names on different devices
+-- to what subsystems they are measuring.
+CREATE TABLE IF NOT EXISTS power_counters (name TEXT UNIQUE, subsystem TEXT);
+
+INSERT OR IGNORE INTO power_counters
+VALUES ('power.VPH_PWR_S5C_S6C_uws', 'cpu_big'),
+  ('power.VPH_PWR_S4C_uws', 'cpu_little'),
+  ('power.VPH_PWR_S2C_S3C_uws', 'soc'),
+  ('power.VPH_PWR_OLED_uws', 'display'),
+  ('power.PPVAR_VPH_PWR_S1A_S9A_S10A_uws', 'soc'),
+  ('power.PPVAR_VPH_PWR_S2A_S3A_uws', 'cpu_big'),
+  ('power.PPVAR_VPH_PWR_S1C_uws', 'cpu_little'),
+  ('power.WCN3998_VDD13 [from PP1304_L2C]_uws', 'wifi'),
+  ('power.PPVAR_VPH_PWR_WLAN_uws', 'wifi'),
+  ('power.PPVAR_VPH_PWR_OLED_uws', 'display'),
+  ('power.PPVAR_VPH_PWR_QTM525_uws', 'cellular'),
+  ('power.PPVAR_VPH_PWR_RF_uws', 'cellular');
+
+-- Convert power counter data into table of events, where each event has
+-- start timestamp, duration and the average power drain during its duration
+-- in Watts.
+-- Note that power counters wrap around at different values on different
+-- devices. When that happens, we ignore the value before overflow, and only
+-- take into account the value after it. This underestimates the actual power
+-- drain between those counters.
+DROP VIEW IF EXISTS drain_in_watts;
+CREATE VIEW drain_in_watts AS
+SELECT name,
+  ts,
+  LEAD(ts) OVER (
+    PARTITION BY track_id
+    ORDER BY ts
+  ) - ts AS dur,
+  CASE
+    WHEN LEAD(value) OVER (
+      PARTITION BY track_id
+      ORDER BY ts
+    ) >= value THEN (
+      LEAD(value) OVER (
+        PARTITION BY track_id
+        ORDER BY ts
+      ) - value
+    )
+    ELSE LEAD(value) OVER (
+      PARTITION BY track_id
+      ORDER BY ts
+    )
+  END / (
+    LEAD(ts) OVER (
+      PARTITION BY track_id
+      ORDER BY ts
+    ) - ts
+  ) * 1e3 AS drain_w
+FROM counter
+  JOIN counter_track ON (counter.track_id = counter_track.id)
+WHERE counter_track.type = 'counter_track'
+  AND name LIKE "power.%";
diff --git a/src/trace_processor/metrics/android/power_profile_data.sql b/src/trace_processor/metrics/android/power_profile_data.sql
index a91d0d7..5ea96ea 100644
--- a/src/trace_processor/metrics/android/power_profile_data.sql
+++ b/src/trace_processor/metrics/android/power_profile_data.sql
@@ -13,7 +13,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-INSERT INTO power_profile VALUES
+INSERT OR REPLACE INTO power_profile VALUES
 ("marlin", 0, 0, 307200, 11.272),
 ("marlin", 0, 0, 384000, 14.842),
 ("marlin", 0, 0, 460800, 18.497),
diff --git a/src/trace_processor/metrics/android/process_metadata.sql b/src/trace_processor/metrics/android/process_metadata.sql
index 3fd5e82..757470e 100644
--- a/src/trace_processor/metrics/android/process_metadata.sql
+++ b/src/trace_processor/metrics/android/process_metadata.sql
@@ -26,7 +26,16 @@
 CREATE TABLE process_metadata_table AS
 SELECT
   process.upid,
-  process.name AS process_name,
+  -- TODO(b/169226092) remove this workaround
+  CASE
+      -- cmdline gets rewritten after fork, if these are still there we must
+      -- have seen a racy capture.
+    WHEN length(process.name) = 15 AND (
+      process.cmdline in ('zygote', 'zygote64', '<pre-initialized>')
+      OR process.cmdline like '%' || process.name)
+    THEN process.cmdline
+    ELSE process.name
+  END AS process_name,
   process.android_appid AS uid,
   CASE WHEN uid_package_count.cnt > 1 THEN TRUE ELSE NULL END AS shared_uid,
   plist.package_name,
diff --git a/src/trace_processor/metrics/android/unmapped_java_symbols.sql b/src/trace_processor/metrics/android/unmapped_java_symbols.sql
deleted file mode 100644
index 1b7f829..0000000
--- a/src/trace_processor/metrics/android/unmapped_java_symbols.sql
+++ /dev/null
@@ -1,81 +0,0 @@
---
--- Copyright 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
---
---     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 RUN_METRIC('android/process_metadata.sql');
-
-CREATE TABLE IF NOT EXISTS types_per_upid AS
-WITH distinct_unmapped_type_names AS (
-  SELECT DISTINCT upid, type_name
-  FROM (
-    SELECT
-      upid,
-      RTRIM(REPLACE(c.name, 'java.lang.Class<', ''), '[]>') AS type_name
-    FROM heap_graph_object o JOIN heap_graph_class c ON o.type_id = c.id
-    WHERE c.deobfuscated_name IS NULL
-  )
-  WHERE type_name NOT IN ('byte', 'char', 'short', 'int', 'long', 'boolean', 'float', 'double')
-  AND type_name NOT LIKE '$Proxy%'
-  AND type_name NOT LIKE 'java.%'
-  AND type_name NOT LIKE 'javax.%'
-  AND type_name NOT LIKE 'j$.%'
-  AND type_name NOT LIKE 'android.%'
-  AND type_name NOT LIKE 'com.android.%'
-  AND type_name NOT LIKE 'sun.%'
-  AND type_name NOT LIKE 'dalvik.%'
-  AND type_name NOT LIKE 'libcore.%'
-  AND LENGTH(type_name) > 0
-)
-SELECT upid, RepeatedField(type_name) AS types
-FROM distinct_unmapped_type_names GROUP BY 1;
-
-CREATE TABLE IF NOT EXISTS fields_per_upid AS
-WITH distinct_unmapped_field_names AS (
-  SELECT DISTINCT o.upid, field_type_name, field_name
-    FROM heap_graph_object o JOIN heap_graph_class c ON o.type_id = c.id
-           JOIN heap_graph_reference USING (reference_set_id)
-  WHERE c.deobfuscated_name IS NULL
-  AND field_name NOT LIKE '$Proxy%'
-  AND field_name NOT LIKE 'java.%'
-  AND field_name NOT LIKE 'javax.%'
-  AND field_name NOT LIKE 'j$.%'
-  AND field_name NOT LIKE 'android.%'
-  AND field_name NOT LIKE 'com.android.%'
-  AND field_name NOT LIKE 'sun.%'
-  AND field_name NOT LIKE 'dalvik.%'
-  AND field_name NOT LIKE 'libcore.%'
-  AND LENGTH(field_name) > 0
-)
-SELECT upid, RepeatedField(
-  UnmappedJavaSymbols_Field(
-    'field_name', field_name,
-    'field_type_name', field_type_name)) AS fields
-FROM distinct_unmapped_field_names GROUP BY 1;
-
-CREATE VIEW IF NOT EXISTS java_symbols_per_process AS
-SELECT UnmappedJavaSymbols_ProcessSymbols(
-  'process_metadata', metadata,
-  'type_name', types,
-  'field', fields
-) types
-FROM types_per_upid
-JOIN process_metadata USING (upid)
-LEFT JOIN fields_per_upid USING (upid);
-
-CREATE VIEW unmapped_java_symbols_output AS
-SELECT UnmappedJavaSymbols(
-  'process_symbols',
-  (SELECT RepeatedField(types) FROM java_symbols_per_process)
-);
diff --git a/src/trace_processor/metrics/chrome/actual_power_by_category.sql b/src/trace_processor/metrics/chrome/actual_power_by_category.sql
new file mode 100644
index 0000000..27b632e
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/actual_power_by_category.sql
@@ -0,0 +1,79 @@
+--
+-- Copyright 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
+--
+--     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.
+--
+
+-- This is a templated metric that takes 3 parameters:
+-- input: name of a table/view which must have columns: id, ts, dur and a
+--   "category" column
+-- output: name of the view that will be created
+-- category: name of the category column in the input table, which will be
+--   preserved in the output
+
+SELECT RUN_METRIC('chrome/chrome_processes.sql');
+SELECT RUN_METRIC('android/power_drain_in_watts.sql');
+
+-- SPAN_JOIN does not yet support non-integer partitions so add an integer
+-- column that corresponds to the power rail name.
+DROP TABLE IF EXISTS power_rail_name_mapping;
+CREATE TABLE power_rail_name_mapping AS
+SELECT DISTINCT name,
+  ROW_NUMBER() OVER() AS idx
+FROM drain_in_watts;
+
+DROP VIEW IF EXISTS mapped_drain_in_watts;
+CREATE VIEW mapped_drain_in_watts AS
+SELECT d.name, ts, dur, drain_w, idx
+FROM drain_in_watts d
+  JOIN power_rail_name_mapping p ON d.name = p.name;
+
+DROP TABLE IF EXISTS real_{{input}}_power;
+CREATE VIRTUAL TABLE real_{{input}}_power USING SPAN_JOIN(
+    {{input}},
+    mapped_drain_in_watts PARTITIONED idx
+);
+
+-- Actual power usage for chrome across the categorised slices contained in the
+-- input table broken down by subsystem.
+DROP VIEW IF EXISTS {{output}};
+CREATE VIEW {{output}} AS
+SELECT s.id,
+  ts,
+  dur,
+  {{category}},
+  subsystem,
+  joules,
+  joules / dur * 1e9 AS drain_w
+FROM (
+    SELECT id,
+      subsystem,
+      SUM(drain_w * dur / 1e9) AS joules
+    FROM real_{{input}}_power
+      JOIN power_counters
+    WHERE real_{{input}}_power.name = power_counters.name
+    GROUP BY id,
+      subsystem
+  ) p
+  JOIN {{input}} s
+WHERE s.id = p.id
+ORDER BY s.id;
+
+SELECT id,
+      subsystem,
+      SUM(drain_w * dur / 1e9) AS joules
+    FROM real_{{input}}_power
+      JOIN power_counters
+    WHERE real_{{input}}_power.name = power_counters.name
+    GROUP BY id,
+      subsystem
diff --git a/src/trace_processor/metrics/chrome/actual_power_by_rail_mode.sql b/src/trace_processor/metrics/chrome/actual_power_by_rail_mode.sql
new file mode 100644
index 0000000..901a07b
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/actual_power_by_rail_mode.sql
@@ -0,0 +1,26 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/rail_modes.sql');
+
+-- Creates a view called real_power_by_rail_mode, containing the actual power
+-- usage for chrome broken down by RAIL Mode (subdivided by subsystem).
+SELECT RUN_METRIC(
+    'chrome/actual_power_by_category.sql',
+    'input', 'combined_overall_rail_slices',
+    'output', 'real_power_by_rail_mode',
+    'category', 'rail_mode'
+  );
diff --git a/src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h b/src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h
deleted file mode 100644
index a7f3e84..0000000
--- a/src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h
+++ /dev/null
@@ -1,2330 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_METRICS_CHROME_ALL_CHROME_METRICS_DESCRIPTOR_H_
-#define SRC_TRACE_PROCESSOR_METRICS_CHROME_ALL_CHROME_METRICS_DESCRIPTOR_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-
-// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
-
-// SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
-// SHA1(protos/perfetto/metrics/chrome/all_chrome_metrics.proto)
-// f2a7367c1cc15c70d581f137af270765ebac547a
-
-// This is the proto AllChromeMetrics encoded as a ProtoFileDescriptor to allow
-// for reflection without libprotobuf full/non-lite protos.
-
-namespace perfetto {
-
-constexpr std::array<uint8_t, 27467> kAllChromeMetricsDescriptor{
-    {0x0a, 0xf3, 0x06, 0x0a, 0x31, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x62, 0x61, 0x74, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xac, 0x06, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42,
-     0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x12, 0x60, 0x0a, 0x10, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0f, 0x62,
-     0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
-     0x72, 0x73, 0x12, 0x66, 0x0a, 0x12, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x73,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74,
-     0x65, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x61,
-     0x74, 0x74, 0x65, 0x72, 0x79, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61,
-     0x74, 0x65, 0x73, 0x52, 0x11, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79,
-     0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xd2,
-     0x01, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69,
-     0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
-     0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x68, 0x61,
-     0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f,
-     0x75, 0x61, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63,
-     0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x55, 0x61, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x61, 0x70, 0x61, 0x63,
-     0x69, 0x74, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18,
-     0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, 0x63, 0x61, 0x70, 0x61, 0x63,
-     0x69, 0x74, 0x79, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x1d,
-     0x0a, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x61,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x63, 0x75, 0x72, 0x72,
-     0x65, 0x6e, 0x74, 0x55, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x72,
-     0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x76, 0x67, 0x5f, 0x75, 0x61, 0x18,
-     0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65,
-     0x6e, 0x74, 0x41, 0x76, 0x67, 0x55, 0x61, 0x1a, 0xf4, 0x02, 0x0a, 0x11,
-     0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x41, 0x67, 0x67, 0x72, 0x65,
-     0x67, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x6f, 0x74,
-     0x61, 0x6c, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x66,
-     0x66, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10,
-     0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f,
-     0x66, 0x66, 0x4e, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61,
-     0x6c, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f,
-     0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f,
-     0x74, 0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x6e, 0x4e,
-     0x73, 0x12, 0x2f, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x64, 0x6f, 0x7a, 0x65, 0x5f, 0x6e,
-     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74,
-     0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x6f, 0x7a, 0x65,
-     0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
-     0x77, 0x61, 0x6b, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x73, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x57, 0x61, 0x6b, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x73, 0x12, 0x19,
-     0x0a, 0x08, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x4e,
-     0x73, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x66, 0x66, 0x5f, 0x6e, 0x73,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x73, 0x6c, 0x65, 0x65,
-     0x70, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x66, 0x66, 0x4e, 0x73,
-     0x12, 0x2b, 0x0a, 0x12, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x63,
-     0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x6e, 0x73, 0x18, 0x07,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x53,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x6e, 0x4e, 0x73, 0x12, 0x2f, 0x0a,
-     0x14, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65,
-     0x6e, 0x5f, 0x64, 0x6f, 0x7a, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x08, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x53, 0x63,
-     0x72, 0x65, 0x65, 0x6e, 0x44, 0x6f, 0x7a, 0x65, 0x4e, 0x73, 0x0a, 0xd4,
-     0x08, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x63,
-     0x70, 0x75, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x8e, 0x08, 0x0a,
-     0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x4c, 0x0a, 0x0c, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x0b,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x1a,
-     0xa8, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12,
-     0x18, 0x0a, 0x07, 0x6d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x63, 0x79, 0x63, 0x6c, 0x65,
-     0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65,
-     0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72,
-     0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x20, 0x0a, 0x0c,
-     0x6d, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x46,
-     0x72, 0x65, 0x71, 0x4b, 0x68, 0x7a, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61,
-     0x78, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65,
-     0x71, 0x4b, 0x68, 0x7a, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x76, 0x67, 0x5f,
-     0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a, 0x18, 0x05, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0a, 0x61, 0x76, 0x67, 0x46, 0x72, 0x65, 0x71, 0x4b,
-     0x68, 0x7a, 0x1a, 0x65, 0x0a, 0x08, 0x43, 0x6f, 0x72, 0x65, 0x44, 0x61,
-     0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x06, 0x1a, 0x67, 0x0a,
-     0x0c, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74,
-     0x61, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a,
-     0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xf4, 0x01, 0x0a, 0x06, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-     0x65, 0x12, 0x43, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x3e,
-     0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x43, 0x6f, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x63, 0x6f,
-     0x72, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x74,
-     0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43,
-     0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x72,
-     0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x08, 0x63,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10,
-     0x04, 0x1a, 0xb9, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a,
-     0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x42, 0x0a, 0x07, 0x74, 0x68,
-     0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x28, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x52, 0x07, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x73, 0x12, 0x3e, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x07,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61,
-     0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x63, 0x6f,
-     0x72, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74,
-     0x61, 0x52, 0x08, 0x63, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x4a,
-     0x04, 0x08, 0x03, 0x10, 0x04, 0x0a, 0xd0, 0x01, 0x0a, 0x35, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
-     0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x85, 0x01, 0x0a,
-     0x15, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x44, 0x69, 0x73, 0x70,
-     0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x34,
-     0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, 0x70, 0x6c,
-     0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61,
-     0x6c, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x64, 0x75, 0x70, 0x6c,
-     0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x5f, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x15, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64,
-     0x0a, 0xc0, 0x03, 0x0a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74,
-     0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf4, 0x02, 0x0a, 0x16, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x49, 0x0a,
-     0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x61, 0x63, 0x6b,
-     0x61, 0x67, 0x65, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x12, 0x59, 0x0a, 0x10, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73,
-     0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x08, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x61, 0x63, 0x6b,
-     0x61, 0x67, 0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x73, 0x46, 0x6f, 0x72, 0x55, 0x69, 0x64, 0x1a, 0x76, 0x0a, 0x07, 0x50,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x70, 0x6b,
-     0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x70, 0x6b,
-     0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12,
-     0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c,
-     0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x65, 0x62,
-     0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10,
-     0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10,
-     0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x0a, 0xeb, 0x08, 0x0a, 0x3c,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x68, 0x65, 0x61, 0x70,
-     0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c,
-     0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x22, 0xe1, 0x07, 0x0a, 0x14, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72,
-     0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x12, 0x5a, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
-     0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61,
-     0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c,
-     0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
-     0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e, 0x73,
-     0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x3e,
-     0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x70, 0x69,
-     0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x0b, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x4e, 0x61,
-     0x6d, 0x65, 0x1a, 0x8e, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79,
-     0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74,
-     0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1f, 0x0a,
-     0x0b, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x74,
-     0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65,
-     0x6c, 0x74, 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x42, 0x79,
-     0x74, 0x65, 0x73, 0x1a, 0xa6, 0x02, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c,
-     0x73, 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,
-     0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68,
-     0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70,
-     0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x41, 0x0a,
-     0x05, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50,
-     0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69,
-     0x74, 0x65, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x05, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x12, 0x4f, 0x0a, 0x0b, 0x73, 0x65, 0x6c, 0x66,
-     0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70,
-     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
-     0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x73, 0x52, 0x0a, 0x73, 0x65, 0x6c, 0x66, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
-     0x73, 0x12, 0x51, 0x0a, 0x0c, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x61,
-     0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72,
-     0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
-     0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x73,
-     0x1a, 0xf2, 0x02, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69,
-     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64,
-     0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f,
-     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12,
-     0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x06,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x09, 0x63, 0x61,
-     0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70,
-     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
-     0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x52, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73,
-     0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18,
-     0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x3b, 0x0a, 0x1b, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6e, 0x6f, 0x6e,
-     0x5f, 0x72, 0x73, 0x73, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61,
-     0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x16, 0x6d, 0x61, 0x78, 0x41, 0x6e, 0x6f, 0x6e, 0x52, 0x73,
-     0x73, 0x41, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x42, 0x79, 0x74, 0x65,
-     0x73, 0x0a, 0x86, 0x0f, 0x0a, 0x31, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x22, 0xe3, 0x0d, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21,
-     0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61,
-     0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a,
-     0x0e, 0x72, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72,
-     0x74, 0x43, 0x70, 0x75, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x28,
-     0x0a, 0x10, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x0e, 0x64, 0x72, 0x61, 0x77, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x64, 0x72, 0x61, 0x77,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x72, 0x61, 0x77, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x4d, 0x61, 0x78, 0x12, 0x24, 0x0a, 0x0e, 0x64, 0x72,
-     0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x72, 0x61, 0x77,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0e,
-     0x64, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x61,
-     0x76, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x64, 0x72,
-     0x61, 0x77, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x76, 0x67, 0x12, 0x1f,
-     0x0a, 0x0b, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
-     0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66,
-     0x6c, 0x75, 0x73, 0x68, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x08, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x4d, 0x61, 0x78,
-     0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x4d, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01,
-     0x52, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x41, 0x76, 0x67, 0x12, 0x2c,
-     0x0a, 0x12, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72,
-     0x65, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x10, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54,
-     0x72, 0x65, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x6d, 0x61, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x72, 0x65, 0x65, 0x4d,
-     0x61, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72,
-     0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x0d,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72,
-     0x65, 0x54, 0x72, 0x65, 0x65, 0x4d, 0x69, 0x6e, 0x12, 0x28, 0x0a, 0x10,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x61, 0x76, 0x67, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x72, 0x65, 0x65, 0x41,
-     0x76, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f,
-     0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x67, 0x70,
-     0x75, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f,
-     0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d,
-     0x61, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x70,
-     0x75, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x4d,
-     0x61, 0x78, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f,
-     0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x70, 0x75, 0x43,
-     0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e,
-     0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x6d, 0x70,
-     0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x12,
-     0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x67, 0x70, 0x75, 0x43, 0x6f, 0x6d,
-     0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x76, 0x67, 0x12, 0x26,
-     0x0a, 0x0f, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0d, 0x52,
-     0x0d, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x14, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d,
-     0x61, 0x78, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d,
-     0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x16, 0x20, 0x01, 0x28,
-     0x01, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x41,
-     0x76, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72,
-     0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x68,
-     0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x68, 0x61, 0x64,
-     0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x74,
-     0x69, 0x6d, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x73,
-     0x68, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
-     0x54, 0x69, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x68, 0x61, 0x64,
-     0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x61,
-     0x76, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x73, 0x68,
-     0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x41,
-     0x76, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f,
-     0x68, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1a, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x48, 0x69,
-     0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x61,
-     0x63, 0x68, 0x65, 0x5f, 0x68, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x18, 0x1b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x61, 0x63, 0x68,
-     0x65, 0x48, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x68, 0x69, 0x74, 0x5f, 0x61, 0x76,
-     0x67, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x63, 0x61, 0x63,
-     0x68, 0x65, 0x48, 0x69, 0x74, 0x41, 0x76, 0x67, 0x12, 0x28, 0x0a, 0x10,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f,
-     0x6d, 0x69, 0x73, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x4d, 0x69,
-     0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x61,
-     0x63, 0x68, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x5f, 0x61, 0x76, 0x67,
-     0x18, 0x1f, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x63, 0x61, 0x63, 0x68,
-     0x65, 0x4d, 0x69, 0x73, 0x73, 0x41, 0x76, 0x67, 0x12, 0x2f, 0x0a, 0x14,
-     0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63, 0x70, 0x75,
-     0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x20, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
-     0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x2f, 0x0a,
-     0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63, 0x70,
-     0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x21, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63,
-     0x73, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x2f,
-     0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63,
-     0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x22,
-     0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
-     0x63, 0x73, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x41, 0x76, 0x67, 0x12,
-     0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f,
-     0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18,
-     0x23, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68,
-     0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78,
-     0x12, 0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
-     0x5f, 0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x24, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70,
-     0x68, 0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x69,
-     0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63,
-     0x73, 0x5f, 0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76,
-     0x67, 0x18, 0x25, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x67, 0x72, 0x61,
-     0x70, 0x68, 0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72,
-     0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x26, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65,
-     0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x65,
-     0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x27, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x65, 0x78,
-     0x74, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x26,
-     0x0a, 0x0f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65,
-     0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x28, 0x20, 0x01, 0x28, 0x01, 0x52,
-     0x0d, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65,
-     0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x29, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x09, 0x61, 0x6c, 0x6c, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x1e,
-     0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x6c, 0x6c,
-     0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6c,
-     0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x2b, 0x20,
-     0x01, 0x28, 0x01, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x22, 0x5a, 0x0a, 0x11, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x48, 0x77, 0x75, 0x69, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x45, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x69,
-     0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52,
-     0x65, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x0a, 0xe0,
-     0x02, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x69,
-     0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x9a, 0x02, 0x0a,
-     0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x40, 0x0a, 0x06, 0x62, 0x75, 0x66,
-     0x66, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49,
-     0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x75, 0x66,
-     0x66, 0x65, 0x72, 0x52, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a,
-     0xc3, 0x01, 0x0a, 0x06, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x12,
-     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76,
-     0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, 0x67, 0x53,
-     0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e,
-     0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74,
-     0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x6d, 0x69,
-     0x6e, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24,
-     0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62,
-     0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c,
-     0x6d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x33, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c,
-     0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74,
-     0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x13, 0x74, 0x6f,
-     0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a, 0x65,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x0a, 0xff, 0x04, 0x0a, 0x39, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68,
-     0x65, 0x61, 0x70, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61,
-     0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf8, 0x03, 0x0a,
-     0x11, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x57, 0x0a, 0x0e, 0x69, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73,
-     0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61,
-     0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x1a,
-     0x75, 0x0a, 0x09, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x6f, 0x62, 0x6a, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x65,
-     0x0a, 0x06, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02,
-     0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73,
-     0x12, 0x4b, 0x0a, 0x0a, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48,
-     0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x54, 0x79, 0x70,
-     0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65,
-     0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xab, 0x01, 0x0a, 0x0d, 0x49, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
-     0x12, 0x0a, 0x04, 0x75, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x04, 0x75, 0x70, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74,
-     0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65,
-     0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69,
-     0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x53, 0x61, 0x6d, 0x70,
-     0x6c, 0x65, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x0a,
-     0x9e, 0x06, 0x0a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x74,
-     0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9b,
-     0x05, 0x0a, 0x0d, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x12, 0x53, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74,
-     0x61, 0x6e, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a,
-     0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73,
-     0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61,
-     0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
-     0x53, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x62, 0x0a, 0x09, 0x48, 0x65, 0x61,
-     0x70, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f,
-     0x6f, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
-     0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xa6, 0x02,
-     0x0a, 0x06, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02,
-     0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73,
-     0x12, 0x1b, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x68, 0x65, 0x61,
-     0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x48, 0x65, 0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x6f, 0x62, 0x6a, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32,
-     0x0a, 0x16, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x61,
-     0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x61, 0x6e, 0x6f, 0x6e,
-     0x52, 0x73, 0x73, 0x41, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x53, 0x69,
-     0x7a, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18,
-     0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74,
-     0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x52,
-     0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x1a, 0xa7, 0x01, 0x0a, 0x0d, 0x49,
-     0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73,
-     0x12, 0x12, 0x0a, 0x04, 0x75, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x04, 0x75, 0x70, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x07,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c,
-     0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52,
-     0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x0a, 0xbb, 0x02, 0x0a,
-     0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b,
-     0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x10, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-     0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x4e, 0x0a, 0x0c, 0x62, 0x79, 0x5f, 0x6f, 0x6f, 0x6d, 0x5f, 0x73,
-     0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4c, 0x6d, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x79,
-     0x4f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x0a, 0x62, 0x79,
-     0x4f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10,
-     0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6d, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
-     0x6f, 0x6f, 0x6d, 0x56, 0x69, 0x63, 0x74, 0x69, 0x6d, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x1a, 0x46, 0x0a, 0x0a, 0x42, 0x79, 0x4f, 0x6f, 0x6d, 0x53,
-     0x63, 0x6f, 0x72, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f, 0x6d, 0x5f,
-     0x73, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72,
-     0x65, 0x41, 0x64, 0x6a, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e,
-     0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x0a, 0xcc, 0x05, 0x0a, 0x37, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f,
-     0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x22, 0xc7, 0x04, 0x0a, 0x16, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x3f, 0x0a, 0x04, 0x6c, 0x6d,
-     0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d,
-     0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4c, 0x6d, 0x6b, 0x52, 0x04, 0x6c, 0x6d, 0x6b, 0x73, 0x1a,
-     0x97, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12,
-     0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f,
-     0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63,
-     0x6f, 0x72, 0x65, 0x41, 0x64, 0x6a, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
-     0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69,
-     0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72,
-     0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0c, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x42,
-     0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x6e, 0x6f, 0x6e,
-     0x5f, 0x72, 0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73,
-     0x73, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x68,
-     0x6d, 0x65, 0x6d, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65,
-     0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x68, 0x6d,
-     0x65, 0x6d, 0x52, 0x73, 0x73, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d,
-     0x0a, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
-     0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x77, 0x61, 0x70,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0xd1, 0x01, 0x0a, 0x03, 0x4c, 0x6d,
-     0x6b, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x6f,
-     0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-     0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x41, 0x64,
-     0x6a, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61,
-     0x70, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0d, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x70, 0x73,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x79, 0x73,
-     0x74, 0x65, 0x6d, 0x5f, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x70,
-     0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x11, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6f, 0x6e, 0x48, 0x65,
-     0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4d, 0x0a, 0x09, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65,
-     0x73, 0x0a, 0x8f, 0x08, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xc9, 0x07, 0x0a, 0x13, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x5c, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xfd, 0x01,
-     0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e,
-     0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x65, 0x0a, 0x12, 0x70, 0x72,
-     0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b,
-     0x64, 0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x72, 0x65,
-     0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f,
-     0x72, 0x69, 0x74, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77,
-     0x6e, 0x1a, 0x87, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69,
-     0x74, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12,
-     0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72,
-     0x69, 0x74, 0x79, 0x12, 0x56, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x08, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x88, 0x03, 0x0a, 0x15,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x47, 0x0a,
-     0x08, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
-     0x72, 0x52, 0x07, 0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73, 0x73, 0x12, 0x47,
-     0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x12,
-     0x40, 0x0a, 0x04, 0x73, 0x77, 0x61, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52,
-     0x04, 0x73, 0x77, 0x61, 0x70, 0x12, 0x50, 0x0a, 0x0d, 0x61, 0x6e, 0x6f,
-     0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x52, 0x0b, 0x61, 0x6e, 0x6f, 0x6e, 0x41, 0x6e, 0x64, 0x53,
-     0x77, 0x61, 0x70, 0x12, 0x49, 0x0a, 0x09, 0x6a, 0x61, 0x76, 0x61, 0x5f,
-     0x68, 0x65, 0x61, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x08, 0x6a, 0x61,
-     0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x1a, 0x3f, 0x0a, 0x07, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12,
-     0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01,
-     0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x76, 0x67, 0x0a,
-     0xa0, 0x06, 0x0a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e, 0x61, 0x67, 0x67, 0x5f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x22, 0xd4, 0x05, 0x0a, 0x1f, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61,
-     0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x65, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55,
-     0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x0d, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x97,
-     0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x56, 0x61,
-     0x6c, 0x75, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e,
-     0x61, 0x6d, 0x65, 0x12, 0x63, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x5f, 0x76,
-     0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x44, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67,
-     0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d,
-     0x6f, 0x72, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x09, 0x6d,
-     0x65, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3, 0x02, 0x0a,
-     0x13, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x08,
-     0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e,
-     0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07,
-     0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73, 0x73, 0x12, 0x51, 0x0a, 0x08, 0x66,
-     0x69, 0x6c, 0x65, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61,
-     0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x66,
-     0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x12, 0x4a, 0x0a, 0x04, 0x73, 0x77,
-     0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65,
-     0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67,
-     0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x56,
-     0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x73, 0x77, 0x61, 0x70, 0x12, 0x5a,
-     0x0a, 0x0d, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73,
-     0x77, 0x61, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65,
-     0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x6f, 0x6e, 0x41,
-     0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x1a, 0x4a, 0x0a, 0x05, 0x56, 0x61,
-     0x6c, 0x75, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f,
-     0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x05, 0x52, 0x08, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65,
-     0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x88,
-     0x02, 0x0a, 0x32, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xc0,
-     0x01, 0x0a, 0x12, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x47, 0x0a,
-     0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x4c, 0x69, 0x73, 0x74, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x61,
-     0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a,
-     0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63,
-     0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
-     0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75,
-     0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
-     0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64,
-     0x65, 0x0a, 0xf0, 0x02, 0x0a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x70, 0x6f, 0x77, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x11, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69,
-     0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0b, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f,
-     0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x2e,
-     0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0a,
-     0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x4e,
-     0x0a, 0x0a, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-     0x70, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b,
-     0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x12,
-     0x1d, 0x0a, 0x0a, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x5f, 0x75, 0x77,
-     0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x65,
-     0x72, 0x67, 0x79, 0x55, 0x77, 0x73, 0x1a, 0x70, 0x0a, 0x0a, 0x50, 0x6f,
-     0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x0b, 0x65, 0x6e, 0x65, 0x72,
-     0x67, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c,
-     0x73, 0x2e, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x52, 0x0a, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x0a, 0xa1, 0x13, 0x0a, 0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f,
-     0x12, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x47, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72,
-     0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
-     0x1a, 0xe0, 0x01, 0x0a, 0x12, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61,
-     0x74, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12,
-     0x24, 0x0a, 0x0e, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64,
-     0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x0c, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x75, 0x72, 0x4e,
-     0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c,
-     0x65, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0d, 0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c, 0x65,
-     0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x75, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65,
-     0x5f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e,
-     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x75, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65,
-     0x53, 0x6c, 0x65, 0x65, 0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x3b,
-     0x0a, 0x1a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69,
-     0x62, 0x6c, 0x65, 0x5f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x75,
-     0x72, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17,
-     0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c,
-     0x65, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x1a,
-     0x35, 0x0a, 0x05, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x12, 0x15, 0x0a, 0x06,
-     0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x05, 0x64, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x64,
-     0x75, 0x72, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
-     0x05, 0x64, 0x75, 0x72, 0x4d, 0x73, 0x1a, 0x80, 0x0b, 0x0a, 0x0c, 0x54,
-     0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
-     0x15, 0x0a, 0x06, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x15,
-     0x0a, 0x06, 0x64, 0x75, 0x72, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01,
-     0x28, 0x01, 0x52, 0x05, 0x64, 0x75, 0x72, 0x4d, 0x73, 0x12, 0x72, 0x0a,
-     0x19, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x5f, 0x62, 0x79, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x61,
-     0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x72, 0x65,
-     0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x15, 0x6d, 0x61, 0x69, 0x6e,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x42, 0x79, 0x54, 0x61, 0x73, 0x6b,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x41, 0x0a, 0x1d, 0x6f, 0x74, 0x68,
-     0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73,
-     0x5f, 0x73, 0x70, 0x61, 0x77, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1a, 0x6f, 0x74,
-     0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73,
-     0x53, 0x70, 0x61, 0x77, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x5f, 0x0a, 0x15, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74,
-     0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
-     0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61,
-     0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53,
-     0x6c, 0x69, 0x63, 0x65, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x41, 0x63,
-     0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
-     0x72, 0x12, 0x66, 0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63,
-     0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16, 0x74,
-     0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x4d, 0x61, 0x69, 0x6e, 0x12, 0x5f, 0x0a,
-     0x15, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61,
-     0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63,
-     0x65, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x41,
-     0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5b,
-     0x0a, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76,
-     0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65,
-     0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69,
-     0x74, 0x79, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x5d, 0x0a, 0x14, 0x74,
-     0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
-     0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x12,
-     0x74, 0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
-     0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x5a, 0x0a, 0x12, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x63, 0x68, 0x6f, 0x72, 0x65, 0x6f, 0x67, 0x72, 0x61,
-     0x70, 0x68, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x11, 0x74, 0x69, 0x6d,
-     0x65, 0x43, 0x68, 0x6f, 0x72, 0x65, 0x6f, 0x67, 0x72, 0x61, 0x70, 0x68,
-     0x65, 0x72, 0x12, 0x66, 0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x62,
-     0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16,
-     0x74, 0x69, 0x6d, 0x65, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x66,
-     0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x69, 0x6e,
-     0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53,
-     0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16, 0x74, 0x69, 0x6d, 0x65,
-     0x44, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x4d, 0x0a, 0x0c, 0x74, 0x6f,
-     0x5f, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x18, 0x12,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63,
-     0x65, 0x52, 0x0a, 0x74, 0x6f, 0x50, 0x6f, 0x73, 0x74, 0x46, 0x6f, 0x72,
-     0x6b, 0x12, 0x62, 0x0a, 0x17, 0x74, 0x6f, 0x5f, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
-     0x6d, 0x61, 0x69, 0x6e, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x14, 0x74, 0x6f, 0x41,
-     0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x4d, 0x61, 0x69, 0x6e, 0x12, 0x5b, 0x0a, 0x13, 0x74, 0x6f, 0x5f,
-     0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
-     0x74, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x11, 0x74, 0x6f, 0x42,
-     0x69, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
-     0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x70,
-     0x6f, 0x73, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x18, 0x10, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52,
-     0x0c, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x6f, 0x73, 0x74, 0x46, 0x6f, 0x72,
-     0x6b, 0x4a, 0x04, 0x08, 0x0c, 0x10, 0x0d, 0x4a, 0x04, 0x08, 0x0d, 0x10,
-     0x0e, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10,
-     0x10, 0x1a, 0x5c, 0x0a, 0x0a, 0x48, 0x73, 0x63, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x12, 0x4e, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f,
-     0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x0b,
-     0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x1a,
-     0xc2, 0x03, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
-     0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x69,
-     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61,
-     0x72, 0x74, 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x7a, 0x79, 0x67, 0x6f,
-     0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x7a, 0x79,
-     0x67, 0x6f, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69,
-     0x74, 0x79, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1b, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x69, 0x74, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
-     0x58, 0x0a, 0x0e, 0x74, 0x6f, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x54, 0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x52, 0x0c, 0x74, 0x6f, 0x46, 0x69, 0x72, 0x73, 0x74,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64,
-     0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x12, 0x42, 0x0a, 0x03, 0x68, 0x73, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x48, 0x73, 0x63, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x52, 0x03, 0x68, 0x73, 0x63, 0x0a, 0xe4, 0x01, 0x0a,
-     0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x75, 0x72,
-     0x66, 0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x9a,
-     0x01, 0x0a, 0x1b, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x75,
-     0x72, 0x66, 0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69,
-     0x73, 0x73, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x69, 0x73, 0x73, 0x65,
-     0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d,
-     0x69, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x77, 0x63, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f,
-     0x6d, 0x69, 0x73, 0x73, 0x65, 0x64, 0x48, 0x77, 0x63, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x73, 0x65,
-     0x64, 0x5f, 0x67, 0x70, 0x75, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x69, 0x73, 0x73,
-     0x65, 0x64, 0x47, 0x70, 0x75, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x0a,
-     0xbb, 0x02, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf5, 0x01,
-     0x0a, 0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x61, 0x73,
-     0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x07, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x1a, 0x9b, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x21, 0x0a,
-     0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
-     0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-     0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12,
-     0x28, 0x0a, 0x10, 0x75, 0x69, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61,
-     0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28,
-     0x09, 0x52, 0x0e, 0x75, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0xe9, 0x06, 0x0a, 0x41, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61,
-     0x74, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x05, 0x0a, 0x1e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69,
-     0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x55, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x1a, 0x95, 0x01, 0x0a, 0x11, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65,
-     0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x72, 0x65,
-     0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x08, 0x63, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a,
-     0x0a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x74, 0x69,
-     0x6d, 0x65, 0x4d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x63, 0x79, 0x63,
-     0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d,
-     0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x6f,
-     0x77, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x6d, 0x61, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x70,
-     0x6f, 0x77, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4d,
-     0x61, 0x68, 0x1a, 0xb1, 0x01, 0x0a, 0x06, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a,
-     0x0b, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x12, 0x72, 0x0a, 0x14, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x63, 0x6f, 0x72,
-     0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x41, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
-     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79,
-     0x70, 0x65, 0x1a, 0x94, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-     0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-     0x72, 0x0a, 0x14, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x62,
-     0x79, 0x5f, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
-     0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65,
-     0x52, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x50, 0x0a, 0x07, 0x74,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
-     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x52, 0x07, 0x74, 0x68,
-     0x72, 0x65, 0x61, 0x64, 0x73, 0x0a, 0x98, 0x04, 0x0a, 0x3b, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e, 0x6d, 0x61, 0x70, 0x70,
-     0x65, 0x64, 0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x79, 0x6d, 0x62,
-     0x6f, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8f,
-     0x03, 0x0a, 0x13, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a,
-     0x61, 0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x5c,
-     0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x79,
-     0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70,
-     0x65, 0x64, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x79, 0x6d,
-     0x62, 0x6f, 0x6c, 0x73, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x1a, 0x4e, 0x0a, 0x05,
-     0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65,
-     0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65,
-     0x12, 0x26, 0x0a, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79,
-     0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x1a, 0xc9, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12,
-     0x52, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-     0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c,
-     0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61,
-     0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x2e, 0x46, 0x69,
-     0x65, 0x6c, 0x64, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4a, 0x04,
-     0x08, 0x03, 0x10, 0x04, 0x0a, 0xfc, 0x01, 0x0a, 0x39, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f,
-     0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xad, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x41,
-     0x0a, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x73,
-     0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x05, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75,
-     0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-     0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75,
-     0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64,
-     0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
-     0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x0a, 0xf0, 0x19, 0x0a, 0x25,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x31, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x62, 0x61, 0x74, 0x74, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x63, 0x70, 0x75, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-     0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x64, 0x69, 0x73,
-     0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3c, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f,
-     0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x31, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-     0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x69, 0x6f, 0x6e,
-     0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a,
-     0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x68, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a, 0x61,
-     0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74,
-     0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x37, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x72,
-     0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x36, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e,
-     0x61, 0x67, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x32, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69,
-     0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x35, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x6f, 0x77, 0x72, 0x61, 0x69,
-     0x6c, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x75, 0x72, 0x66, 0x61,
-     0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x41, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e,
-     0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f,
-     0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75,
-     0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x22, 0xe6, 0x02, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74,
-     0x72, 0x61, 0x63, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x4e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f,
-     0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x55, 0x75, 0x69, 0x64, 0x12, 0x3a, 0x0a,
-     0x19, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x62, 0x75, 0x69,
-     0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69,
-     0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69,
-     0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x49, 0x0a,
-     0x21, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64, 0x5f, 0x74, 0x72, 0x69, 0x67,
-     0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64,
-     0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x75,
-     0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
-     0x12, 0x28, 0x0a, 0x10, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x69,
-     0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x69, 0x7a,
-     0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x18,
-     0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x75,
-     0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
-     0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x11, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x53, 0x65, 0x73, 0x73,
-     0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10,
-     0x02, 0x22, 0x82, 0x0e, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, 0x0c, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x62, 0x61, 0x74, 0x74, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0b, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x12, 0x42, 0x0a, 0x0b,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x63, 0x70, 0x75, 0x18,
-     0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x12, 0x45, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x12, 0x5c, 0x0a, 0x11, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e, 0x61, 0x67, 0x67,
-     0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74,
-     0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x55, 0x6e, 0x61, 0x67,
-     0x67, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69, 0x73,
-     0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63,
-     0x6b, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x12, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x5f, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e,
-     0x12, 0x42, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f,
-     0x6c, 0x6d, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c,
-     0x6d, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x12, 0x4d, 0x0a, 0x10,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70, 0x6f, 0x77, 0x72,
-     0x61, 0x69, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0f,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x72, 0x61,
-     0x69, 0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
-     0x5b, 0x0a, 0x16, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x66,
-     0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
-     0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
-     0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x14,
-     0x68, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43,
-     0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0e,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x54, 0x0a, 0x13, 0x75,
-     0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x73, 0x79, 0x6d,
-     0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x73, 0x52, 0x12, 0x75, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69,
-     0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x46, 0x0a,
-     0x0f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73,
-     0x74, 0x61, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61,
-     0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x6a, 0x61, 0x76, 0x61,
-     0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x52, 0x0a,
-     0x13, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x68,
-     0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x15, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76,
-     0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72,
-     0x61, 0x6d, 0x52, 0x11, 0x6a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70,
-     0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x55, 0x0a,
-     0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6c, 0x6d, 0x6b,
-     0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x12, 0x58, 0x0a, 0x15, 0x75, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64,
-     0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61,
-     0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x52, 0x13, 0x75,
-     0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61, 0x76, 0x61, 0x53,
-     0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x52, 0x0a, 0x13, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x22, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x48, 0x77, 0x75, 0x69, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52,
-     0x11, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x48, 0x77, 0x75, 0x69,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x69,
-     0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x44, 0x69, 0x73,
-     0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52,
-     0x0e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x12, 0x4f, 0x0a, 0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x61, 0x73,
-     0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x10, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73,
-     0x12, 0x6f, 0x0a, 0x1c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f,
-     0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f,
-     0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x18, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69,
-     0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x18, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x63, 0x0a, 0x16, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x5f, 0x73, 0x75, 0x72, 0x66, 0x61, 0x63, 0x65,
-     0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x75, 0x72, 0x66, 0x61, 0x63, 0x65, 0x66, 0x6c,
-     0x69, 0x6e, 0x67, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52,
-     0x15, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x75, 0x72, 0x66,
-     0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2a, 0x06,
-     0x08, 0xc2, 0x03, 0x10, 0xf4, 0x03, 0x2a, 0x06, 0x08, 0xf4, 0x03, 0x10,
-     0xe9, 0x07, 0x2a, 0x06, 0x08, 0xe9, 0x07, 0x10, 0xd1, 0x0f, 0x4a, 0x04,
-     0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x4a, 0x04,
-     0x08, 0x0d, 0x10, 0x0e, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x0a, 0x9b,
-     0x3b, 0x0a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72,
-     0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x62, 0x75, 0x66, 0x22, 0x4d, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65,
-     0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x53, 0x65,
-     0x74, 0x12, 0x38, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69,
-     0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72,
-     0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22,
-     0xe4, 0x04, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a,
-     0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18,
-     0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e,
-     0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x75, 0x62,
-     0x6c, 0x69, 0x63, 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e,
-     0x63, 0x79, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x05, 0x52, 0x10, 0x70, 0x75,
-     0x62, 0x6c, 0x69, 0x63, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e,
-     0x63, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x64,
-     0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20,
-     0x03, 0x28, 0x05, 0x52, 0x0e, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x70,
-     0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x43, 0x0a, 0x0c, 0x6d,
-     0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
-     0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-     0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-     0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72,
-     0x6f, 0x74, 0x6f, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-     0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d,
-     0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65,
-     0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74,
-     0x6f, 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12,
-     0x41, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x06,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-     0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
-     0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
-     0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x07, 0x73,
-     0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78,
-     0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
-     0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50,
-     0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
-     0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67,
-     0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-     0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
-     0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f,
-     0x64, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0e,
-     0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e,
-     0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78,
-     0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x74,
-     0x61, 0x78, 0x22, 0xb9, 0x06, 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x63, 0x72,
-     0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12,
-     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x66, 0x69,
-     0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e,
-     0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73,
-     0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f,
-     0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x43, 0x0a, 0x09, 0x65,
-     0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,
-     0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72,
-     0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e,
-     0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0b, 0x6e, 0x65, 0x73, 0x74,
-     0x65, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52,
-     0x0a, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12,
-     0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65,
-     0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f,
-     0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-     0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
-     0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, 0x6e,
-     0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x65, 0x78,
-     0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x6e, 0x67,
-     0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x6f,
-     0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-     0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72,
-     0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
-     0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x65, 0x78,
-     0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65,
-     0x12, 0x44, 0x0a, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x64, 0x65,
-     0x63, 0x6c, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67,
-     0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-     0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52,
-     0x09, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x63, 0x6c, 0x12, 0x39,
-     0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65,
-     0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-     0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x55, 0x0a,
-     0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x72, 0x61,
-     0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e,
-     0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-     0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x65,
-     0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x72,
-     0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65,
-     0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64,
-     0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52,
-     0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, 0x6d,
-     0x65, 0x1a, 0x7a, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
-     0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73,
-     0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05,
-     0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12,
-     0x40, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-     0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
-     0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67,
-     0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73,
-     0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14,
-     0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03,
-     0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65,
-     0x6e, 0x64, 0x22, 0x7c, 0x0a, 0x15, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
-     0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74,
-     0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24,
-     0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-     0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-     0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65,
-     0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08,
-     0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x98, 0x06, 0x0a,
-     0x14, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
-     0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a,
-     0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d,
-     0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e,
-     0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x05, 0x6c, 0x61, 0x62,
-     0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x67,
-     0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-     0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
-     0x4c, 0x61, 0x62, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c,
-     0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01,
-     0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,
-     0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72,
-     0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
-     0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65,
-     0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x08, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
-     0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
-     0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
-     0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c,
-     0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f,
-     0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52,
-     0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12,
-     0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
-     0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-     0x73, 0x22, 0xb6, 0x02, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f,
-     0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x55, 0x42, 0x4c,
-     0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f,
-     0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x54,
-     0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x03, 0x12,
-     0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54,
-     0x36, 0x34, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45,
-     0x5f, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c,
-     0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34,
-     0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46,
-     0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09,
-     0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x08, 0x12,
-     0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49,
-     0x4e, 0x47, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45,
-     0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c,
-     0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45,
-     0x10, 0x0b, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42,
-     0x59, 0x54, 0x45, 0x53, 0x10, 0x0c, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59,
-     0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x0d, 0x12,
-     0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d,
-     0x10, 0x0e, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53,
-     0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x0f, 0x12, 0x11, 0x0a,
-     0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44,
-     0x36, 0x34, 0x10, 0x10, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45,
-     0x5f, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x11, 0x12, 0x0f, 0x0a,
-     0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34,
-     0x10, 0x12, 0x22, 0x43, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12,
-     0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x54,
-     0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4c,
-     0x41, 0x42, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45,
-     0x44, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c,
-     0x5f, 0x52, 0x45, 0x50, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x22,
-     0x63, 0x0a, 0x14, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
-     0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x13, 0x45, 0x6e, 0x75,
-     0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50,
-     0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-     0x12, 0x3f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e,
-     0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72,
-     0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05,
-     0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x6f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
-     0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-     0x73, 0x12, 0x5d, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
-     0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x36, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d,
-     0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72,
-     0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65,
-     0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x72,
-     0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65,
-     0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64,
-     0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52,
-     0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, 0x6d,
-     0x65, 0x1a, 0x3b, 0x0a, 0x11, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73,
-     0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14,
-     0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03,
-     0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65,
-     0x6e, 0x64, 0x22, 0x83, 0x01, 0x0a, 0x18, 0x45, 0x6e, 0x75, 0x6d, 0x56,
-     0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-     0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65,
-     0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d,
-     0x62, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67,
-     0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-     0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65,
-     0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x72,
-     0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-     0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
-     0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f,
-     0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-     0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52,
-     0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x39, 0x0a, 0x07, 0x6f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69,
-     0x63, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x89, 0x02, 0x0a, 0x15, 0x4d,
-     0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
-     0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75,
-     0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
-     0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79,
-     0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75,
-     0x74, 0x70, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x07,
-     0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68,
-     0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x10, 0x63, 0x6c,
-     0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69,
-     0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61,
-     0x6c, 0x73, 0x65, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53,
-     0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x10,
-     0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61,
-     0x6d, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05,
-     0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65,
-     0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x92,
-     0x09, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x70,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x0b, 0x6a, 0x61, 0x76, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6f, 0x75,
-     0x74, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, 0x61, 0x76,
-     0x61, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x5f,
-     0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c,
-     0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61,
-     0x6c, 0x73, 0x65, 0x52, 0x11, 0x6a, 0x61, 0x76, 0x61, 0x4d, 0x75, 0x6c,
-     0x74, 0x69, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x44,
-     0x0a, 0x1d, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72,
-     0x61, 0x74, 0x65, 0x5f, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x5f, 0x61,
-     0x6e, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28,
-     0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x19, 0x6a, 0x61, 0x76, 0x61, 0x47,
-     0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x71, 0x75, 0x61, 0x6c,
-     0x73, 0x41, 0x6e, 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3a, 0x0a, 0x16,
-     0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f,
-     0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, 0x74, 0x66, 0x38, 0x18, 0x1b,
-     0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52,
-     0x13, 0x6a, 0x61, 0x76, 0x61, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x43,
-     0x68, 0x65, 0x63, 0x6b, 0x55, 0x74, 0x66, 0x38, 0x12, 0x53, 0x0a, 0x0c,
-     0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x5f, 0x66, 0x6f, 0x72,
-     0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f,
-     0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-     0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-     0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, 0x64,
-     0x65, 0x3a, 0x05, 0x53, 0x50, 0x45, 0x45, 0x44, 0x52, 0x0b, 0x6f, 0x70,
-     0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x46, 0x6f, 0x72, 0x12, 0x1d, 0x0a,
-     0x0a, 0x67, 0x6f, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18,
-     0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x50, 0x61, 0x63,
-     0x6b, 0x61, 0x67, 0x65, 0x12, 0x35, 0x0a, 0x13, 0x63, 0x63, 0x5f, 0x67,
-     0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,
-     0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66,
-     0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x63, 0x63, 0x47, 0x65, 0x6e, 0x65,
-     0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12,
-     0x39, 0x0a, 0x15, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, 0x6e, 0x65,
-     0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
-     0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73,
-     0x65, 0x52, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x47, 0x65, 0x6e, 0x65, 0x72,
-     0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x35,
-     0x0a, 0x13, 0x70, 0x79, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63,
-     0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x12, 0x20,
-     0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11,
-     0x70, 0x79, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72,
-     0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x14, 0x70, 0x68, 0x70,
-     0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72,
-     0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x08, 0x3a,
-     0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x12, 0x70, 0x68, 0x70, 0x47,
-     0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
-     0x65, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63,
-     0x61, 0x74, 0x65, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05,
-     0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65,
-     0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2f, 0x0a, 0x10, 0x63, 0x63, 0x5f,
-     0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x72, 0x65, 0x6e, 0x61,
-     0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c,
-     0x73, 0x65, 0x52, 0x0e, 0x63, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
-     0x41, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x62,
-     0x6a, 0x63, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x65,
-     0x66, 0x69, 0x78, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f,
-     0x62, 0x6a, 0x63, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66,
-     0x69, 0x78, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x73, 0x68, 0x61, 0x72, 0x70,
-     0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x25,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x73, 0x68, 0x61, 0x72, 0x70,
-     0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a,
-     0x0c, 0x73, 0x77, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69,
-     0x78, 0x18, 0x27, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x77, 0x69,
-     0x66, 0x74, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x28, 0x0a, 0x10,
-     0x70, 0x68, 0x70, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72,
-     0x65, 0x66, 0x69, 0x78, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
-     0x70, 0x68, 0x70, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66,
-     0x69, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x68, 0x70, 0x5f, 0x6e, 0x61,
-     0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x29, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x0c, 0x70, 0x68, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
-     0x61, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x68, 0x70, 0x5f, 0x6d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x14, 0x70, 0x68, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-     0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a,
-     0x0c, 0x72, 0x75, 0x62, 0x79, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x75, 0x62,
-     0x79, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x58, 0x0a, 0x14,
-     0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65,
-     0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e,
-     0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74,
-     0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x22, 0x3a, 0x0a, 0x0c, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69,
-     0x7a, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x50,
-     0x45, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x44,
-     0x45, 0x5f, 0x53, 0x49, 0x5a, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c,
-     0x4c, 0x49, 0x54, 0x45, 0x5f, 0x52, 0x55, 0x4e, 0x54, 0x49, 0x4d, 0x45,
-     0x10, 0x03, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80,
-     0x02, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, 0x22, 0xd1, 0x02, 0x0a, 0x0e,
-     0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-     0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66,
-     0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a,
-     0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x14, 0x6d, 0x65, 0x73, 0x73,
-     0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x57, 0x69, 0x72, 0x65, 0x46, 0x6f,
-     0x72, 0x6d, 0x61, 0x74, 0x12, 0x4c, 0x0a, 0x1f, 0x6e, 0x6f, 0x5f, 0x73,
-     0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73,
-     0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66,
-     0x61, 0x6c, 0x73, 0x65, 0x52, 0x1c, 0x6e, 0x6f, 0x53, 0x74, 0x61, 0x6e,
-     0x64, 0x61, 0x72, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-     0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x25,
-     0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73,
-     0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65,
-     0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x5f, 0x65, 0x6e, 0x74,
-     0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61,
-     0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e,
-     0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f,
-     0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-     0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-     0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a,
-     0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22,
-     0xe2, 0x03, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x63, 0x74, 0x79, 0x70,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67, 0x6f,
-     0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-     0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x73, 0x2e, 0x43, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x06, 0x53, 0x54,
-     0x52, 0x49, 0x4e, 0x47, 0x52, 0x05, 0x63, 0x74, 0x79, 0x70, 0x65, 0x12,
-     0x16, 0x0a, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x12,
-     0x47, 0x0a, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69,
-     0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a,
-     0x53, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f,
-     0x52, 0x4d, 0x41, 0x4c, 0x52, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65,
-     0x12, 0x19, 0x0a, 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x05, 0x20, 0x01,
-     0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x04, 0x6c,
-     0x61, 0x7a, 0x79, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65,
-     0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a,
-     0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72,
-     0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x04, 0x77, 0x65,
-     0x61, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61,
-     0x6c, 0x73, 0x65, 0x52, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x58, 0x0a,
-     0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74,
-     0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-     0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55,
-     0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64,
-     0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x22, 0x2f, 0x0a, 0x05, 0x43, 0x54, 0x79, 0x70, 0x65,
-     0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00,
-     0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x52, 0x44, 0x10, 0x01, 0x12, 0x10,
-     0x0a, 0x0c, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x49, 0x45,
-     0x43, 0x45, 0x10, 0x02, 0x22, 0x35, 0x0a, 0x06, 0x4a, 0x53, 0x54, 0x79,
-     0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52,
-     0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x53, 0x5f,
-     0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09,
-     0x4a, 0x53, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x02, 0x2a,
-     0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04,
-     0x08, 0x04, 0x10, 0x05, 0x22, 0x73, 0x0a, 0x0c, 0x4f, 0x6e, 0x65, 0x6f,
-     0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x14,
-     0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65,
-     0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e,
-     0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74,
-     0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80,
-     0x02, 0x22, 0xc0, 0x01, 0x0a, 0x0b, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c,
-     0x6f, 0x77, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x08, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x41, 0x6c, 0x69,
-     0x61, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63,
-     0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05,
-     0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65,
-     0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74,
-     0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70,
-     0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a,
-     0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04,
-     0x08, 0x05, 0x10, 0x06, 0x22, 0x9e, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x75,
-     0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-     0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61,
-     0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66,
-     0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63,
-     0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65,
-     0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
-     0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09,
-     0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x9c, 0x01,
-     0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x70, 0x74,
-     0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72,
-     0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08,
-     0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70,
-     0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75,
-     0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64,
-     0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65,
-     0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02,
-     0x22, 0xe0, 0x02, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f,
-     0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65,
-     0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x21, 0x20, 0x01,
-     0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64,
-     0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x71, 0x0a,
-     0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79,
-     0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0e,
-     0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f,
-     0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x64, 0x65,
-     0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65,
-     0x6c, 0x3a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e,
-     0x43, 0x59, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x10,
-     0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c,
-     0x65, 0x76, 0x65, 0x6c, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e,
-     0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70,
-     0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65,
-     0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
-     0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72,
-     0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50,
-     0x0a, 0x10, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63,
-     0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x44,
-     0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x55, 0x4e,
-     0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4e,
-     0x4f, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43,
-     0x54, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x44, 0x45, 0x4d,
-     0x50, 0x4f, 0x54, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x2a, 0x09, 0x08, 0xe8,
-     0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x9a, 0x03, 0x0a, 0x13,
-     0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65,
-     0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e,
-     0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70,
-     0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
-     0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x52, 0x04, 0x6e, 0x61,
-     0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
-     0x66, 0x69, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
-     0x66, 0x69, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a,
-     0x12, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e,
-     0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x04, 0x52, 0x10, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x49,
-     0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6e,
-     0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x5f,
-     0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x10, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74,
-     0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75,
-     0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20,
-     0x01, 0x28, 0x01, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56,
-     0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69,
-     0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01,
-     0x28, 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
-     0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65,
-     0x67, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67,
-     0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x4a, 0x0a, 0x08,
-     0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09,
-     0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20,
-     0x02, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72,
-     0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65,
-     0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x02, 0x28, 0x08, 0x52,
-     0x0b, 0x69, 0x73, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
-     0x22, 0xa7, 0x02, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43,
-     0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x08, 0x6c,
-     0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x28, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, 0x72,
-     0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c,
-     0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63,
-     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xce, 0x01, 0x0a, 0x08, 0x4c, 0x6f,
-     0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61,
-     0x74, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01,
-     0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x04, 0x73, 0x70,
-     0x61, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01,
-     0x52, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x6c, 0x65,
-     0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e,
-     0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x65,
-     0x61, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,
-     0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e,
-     0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69,
-     0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a,
-     0x0a, 0x19, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65,
-     0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
-     0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x6c,
-     0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68,
-     0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xd1,
-     0x01, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64,
-     0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x0a,
-     0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-     0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47,
-     0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65,
-     0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
-     0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
-     0x69, 0x6f, 0x6e, 0x1a, 0x6d, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74,
-     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74,
-     0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52,
-     0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75,
-     0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69,
-     0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18,
-     0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e,
-     0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x42, 0x8f, 0x01, 0x0a, 0x13, 0x63,
-     0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x10, 0x44, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x48, 0x01, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-     0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x64, 0x65, 0x73,
-     0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x3b, 0x64, 0x65, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03,
-     0x47, 0x50, 0x42, 0xaa, 0x02, 0x1a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65,
-     0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0xda, 0x01, 0x0a,
-     0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69,
-     0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67,
-     0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-     0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
-     0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x33, 0x0a, 0x04, 0x75,
-     0x6e, 0x69, 0x74, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69,
-     0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd1,
-     0x86, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74,
-     0x3a, 0x53, 0x0a, 0x14, 0x69, 0x6d, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6d,
-     0x65, 0x6e, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-     0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
-     0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd2, 0x86, 0x03, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x14, 0x69, 0x6d, 0x70, 0x72, 0x6f, 0x76, 0x65,
-     0x6d, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
-     0x6e, 0x0a, 0xc5, 0x02, 0x0a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
-     0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x65, 0x72, 0x72,
-     0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x2c, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x75,
-     0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc8, 0x01, 0x0a, 0x12, 0x43,
-     0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x38, 0x0a, 0x0a, 0x61, 0x6c, 0x6c,
-     0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x03, 0x42, 0x19, 0x8a, 0xb5, 0x18, 0x15, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x5f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x73, 0x42, 0x65,
-     0x74, 0x74, 0x65, 0x72, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x45, 0x72, 0x72,
-     0x6f, 0x72, 0x73, 0x12, 0x36, 0x0a, 0x09, 0x6a, 0x73, 0x5f, 0x65, 0x72,
-     0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x19,
-     0x8a, 0xb5, 0x18, 0x15, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x6d,
-     0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x73, 0x42, 0x65, 0x74, 0x74, 0x65,
-     0x72, 0x52, 0x08, 0x6a, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12,
-     0x40, 0x0a, 0x0e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x65,
-     0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42,
-     0x19, 0x8a, 0xb5, 0x18, 0x15, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73,
-     0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x73, 0x42, 0x65, 0x74, 0x74,
-     0x65, 0x72, 0x52, 0x0d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x45,
-     0x72, 0x72, 0x6f, 0x72, 0x73, 0x0a, 0xa3, 0x02, 0x0a, 0x37, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x68,
-     0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x68, 0x72,
-     0x6f, 0x6d, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x25,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x63, 0x6f,
-     0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x3a, 0x75, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f,
-     0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x12, 0x1d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0xe9, 0x07, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6e,
-     0x73, 0x6f, 0x6c, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65,
-     0x45, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63}};
-
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_METRICS_CHROME_ALL_CHROME_METRICS_DESCRIPTOR_H_
diff --git a/src/trace_processor/metrics/chrome/chrome_event_metadata.sql b/src/trace_processor/metrics/chrome/chrome_event_metadata.sql
new file mode 100644
index 0000000..cc23004
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/chrome_event_metadata.sql
@@ -0,0 +1,39 @@
+--
+-- Copyright 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
+--
+--     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.
+--
+
+-- Creates a view containing name-value pairs extracted from
+-- chrome_event.metadata. Some names can have multiple values:
+-- e.g. trace-category
+DROP VIEW IF EXISTS chrome_event_metadata;
+CREATE VIEW chrome_event_metadata AS
+WITH metadata (arg_set_id) AS (
+  SELECT arg_set_id
+  FROM raw
+  WHERE name = "chrome_event.metadata"
+)
+-- TODO(b/173201788): Once this is fixed, extract all the fields.
+SELECT 'os-name' AS name,
+  EXTRACT_ARG(metadata.arg_set_id, 'os-name') AS value
+FROM metadata
+UNION
+SELECT "trace-category" AS name,
+  value AS category
+FROM metadata, json_each(
+    json_extract(
+      EXTRACT_ARG(metadata.arg_set_id, "trace-config"),
+      '$.included_categories'
+    )
+  );
diff --git a/src/trace_processor/metrics/chrome/chrome_processes.sql b/src/trace_processor/metrics/chrome/chrome_processes.sql
index 9287082..f2ff5c4 100644
--- a/src/trace_processor/metrics/chrome/chrome_processes.sql
+++ b/src/trace_processor/metrics/chrome/chrome_processes.sql
@@ -36,6 +36,7 @@
     name = 'Zygote' OR
     name = 'SandboxHelper' OR
     name = 'Gpu' OR
+    name = 'GPU Process' OR
     name = 'PpapiPlugin' OR
     name = 'PpapiBroker');
 
diff --git a/src/trace_processor/metrics/chrome/chrome_thread_slice_with_cpu_time.sql b/src/trace_processor/metrics/chrome/chrome_thread_slice_with_cpu_time.sql
new file mode 100644
index 0000000..16d69b2
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/chrome_thread_slice_with_cpu_time.sql
@@ -0,0 +1,102 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/chrome_processes.sql');
+
+-- Grab all the thread tracks which are found in chrome threads.
+DROP VIEW IF EXISTS chrome_track;
+
+CREATE VIEW chrome_track AS
+  SELECT
+    *
+  FROM thread_track
+  WHERE utid IN (SELECT utid FROM chrome_thread);
+
+-- From all the chrome thread tracks select all the slice details as well as
+-- the utid of the track so we can join with counter table later.
+DROP VIEW IF EXISTS chrome_slice;
+
+CREATE VIEW chrome_slice AS
+  SELECT
+    slice.*,
+    chrome_track.utid
+  FROM
+    slice JOIN
+    chrome_track ON
+        chrome_track.id = slice.track_id
+  WHERE
+    track_id in (SELECT id FROM chrome_track);
+
+-- Using utid join the thread_counter_track to chrome thread slices. This allows
+-- the filtering of the counter table to only counters associated to these
+-- threads.
+DROP VIEW IF EXISTS chrome_slice_and_counter_track;
+
+CREATE VIEW chrome_slice_and_counter_track AS
+  SELECT
+    s.*,
+    thread_counter_track.id as counter_track_id,
+    thread_counter_track.name as counter_name
+  FROM
+    chrome_slice s JOIN
+    thread_counter_track ON
+        thread_counter_track.utid = s.utid AND
+        thread_counter_track.name = "thread_time";
+
+-- Join each slice with the recorded value at the beginning and the end, as
+-- well as computing the total CPU time each slice took.
+--
+-- We use MIN and MAX inside because sometimes nested slices will have the exact
+-- same timestamp and we need to select one, there is nothing tying a particular
+-- counter value to which slice generated it so we always choose the minimum for
+-- the start on ties and the maximum for ties on the end of the slice. This
+-- means this is always an overestimate, but events being emitted at exactly the
+-- same timestamp is relatively rare so shouldn't cause to much inflation.
+DROP VIEW IF EXISTS chrome_thread_slice_with_cpu_time;
+
+CREATE VIEW chrome_thread_slice_with_cpu_time AS
+  SELECT
+    end_cpu_time - start_cpu_time AS slice_cpu_time,
+    *
+  FROM (
+    SELECT
+      s.*,
+      min_counter.start_cpu_time
+    FROM
+      chrome_slice_and_counter_track s LEFT JOIN (
+        SELECT
+          ts,
+          track_id,
+          MIN(value) AS start_cpu_time
+        FROM counter
+        GROUP BY 1, 2
+      ) min_counter ON
+          min_counter.ts = s.ts AND min_counter.track_id = s.counter_track_id
+  ) min_and_slice LEFT JOIN (
+    SELECT
+      ts,
+      track_id,
+      MAX(value) AS end_cpu_time
+    FROM counter
+    GROUP BY 1, 2
+  ) max_counter ON
+      max_counter.ts =
+          CASE WHEN min_and_slice.dur >= 0 THEN
+            min_and_slice.ts + min_and_slice.dur
+          ELSE
+            min_and_slice.ts
+          END AND
+      max_counter.track_id = min_and_slice.counter_track_id;
diff --git a/src/trace_processor/metrics/chrome/console_error_metric.sql b/src/trace_processor/metrics/chrome/console_error_metric.sql
deleted file mode 100644
index 809ade3..0000000
--- a/src/trace_processor/metrics/chrome/console_error_metric.sql
+++ /dev/null
@@ -1,31 +0,0 @@
--- Copyright 2019 Google LLC.
--- SPDX-License-Identifier: Apache-2.0
-
-CREATE VIEW console_error_events AS
-SELECT args.string_value as source
-from slices
-inner join args using(arg_set_id)
-where slices.name = "ConsoleMessage::Error"
-  and slices.category = "blink.console"
-  and args.flat_key = "debug.source"
-UNION ALL
-SELECT "JS" AS source
-FROM slices
-WHERE slices.category = 'v8.console' AND (
-  slices.name = 'V8ConsoleMessage::Exception' OR
-  slices.name = 'V8ConsoleMessage::Error' OR
-  slices.name = 'V8ConsoleMessage::Assert'
-);
-
-CREATE VIEW console_error_metric AS
-SELECT
-  (SELECT COUNT(*) FROM console_error_events) as all_errors,
-  (SELECT COUNT(*) FROM console_error_events where source = "JS") as js_errors,
-  (SELECT COUNT(*) FROM console_error_events where source = "Network") as network_errors;
-
-CREATE VIEW console_error_metric_output AS
-SELECT ConsoleErrorMetric(
-  'all_errors', all_errors,
-  'js_errors', js_errors,
-  'network_errors', network_errors)
-FROM console_error_metric
diff --git a/src/trace_processor/metrics/chrome/cpu_time_by_category.sql b/src/trace_processor/metrics/chrome/cpu_time_by_category.sql
new file mode 100644
index 0000000..2981ebd
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/cpu_time_by_category.sql
@@ -0,0 +1,71 @@
+--
+-- Copyright 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
+--
+--     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.
+--
+
+-- This is a templated metric that takes 3 parameters:
+-- input: name of a table/view which must have columns: id, ts, dur and a
+--   "category" column
+-- output: name of the view that will be created
+-- category: name of the category column in the input table, which will be
+--   preserved in the output
+
+SELECT RUN_METRIC('chrome/chrome_processes.sql') ;
+
+-- CPU time slices for Chrome threads.
+DROP VIEW IF EXISTS chrome_cpu_slices;
+CREATE VIEW chrome_cpu_slices AS
+SELECT counters.id AS counter_id,
+  track_id,
+  ts,
+  lead(ts) OVER (
+    PARTITION BY track_id
+    ORDER BY ts
+  ) - ts AS dur,
+  CAST(
+    lead(value) OVER (
+      PARTITION BY track_id
+      ORDER BY ts
+    ) - value AS "INT64"
+  ) AS cpu_dur
+FROM counters,
+  (
+    SELECT thread_counter_track.id
+    FROM chrome_thread
+      JOIN thread_counter_track ON chrome_thread.utid = thread_counter_track.utid
+  ) AS t
+WHERE t.id = track_id;
+
+DROP TABLE IF EXISTS {{input}}_cpu_time;
+CREATE VIRTUAL TABLE {{input}}_cpu_time USING SPAN_JOIN(
+  {{input}},
+  chrome_cpu_slices PARTITIONED track_id
+);
+
+-- View containing the CPU time used (across all cores) for each category slice
+-- from input.
+-- This will slightly overestimate the CPU time for some category slices as the
+-- cpu time slices don't always line up with the category slices. However the
+-- CPU slices are small enough this makes very little difference.
+DROP VIEW IF EXISTS {{output}};
+CREATE VIEW {{output}} AS
+SELECT s.id,
+  s.ts,
+  s.dur,
+  s.{{category}},
+  SUM(cpu_dur) AS cpu_dur
+FROM {{input}}_cpu_time r
+  JOIN {{input}} s
+WHERE r.id = s.id
+GROUP BY r.id;
diff --git a/src/trace_processor/metrics/chrome/cpu_time_by_rail_mode.sql b/src/trace_processor/metrics/chrome/cpu_time_by_rail_mode.sql
new file mode 100644
index 0000000..426bd25
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/cpu_time_by_rail_mode.sql
@@ -0,0 +1,25 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/rail_modes.sql') AS suppress_query_output;
+
+-- Creates a view cpu_time_by_rail_mode containing the CPU time used (across all
+-- cores) for each RAIL Mode slice in combined_overall_rail_slices.
+SELECT RUN_METRIC('chrome/cpu_time_by_category.sql',
+    'input', 'combined_overall_rail_slices',
+    'output', 'cpu_time_by_rail_mode',
+    'category', 'rail_mode'
+  );
diff --git a/src/trace_processor/metrics/chrome/estimated_power_by_category.sql b/src/trace_processor/metrics/chrome/estimated_power_by_category.sql
new file mode 100644
index 0000000..506d2a1
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/estimated_power_by_category.sql
@@ -0,0 +1,71 @@
+--
+-- Copyright 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
+--
+--     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.
+--
+
+-- This is a templated metric that takes 3 parameters:
+-- input: name of a table/view which must have columns: id, ts, dur and a
+--   "category" column
+-- output: name of the view that will be created
+-- category: name of the category column in the input table, which will be
+--   preserved in the output
+
+SELECT RUN_METRIC('chrome/chrome_processes.sql');
+SELECT RUN_METRIC('android/android_proxy_power.sql');
+
+-- View containing estimated power slices broken down by cpu.
+DROP VIEW IF EXISTS power_per_chrome_thread;
+CREATE VIEW power_per_chrome_thread AS
+SELECT ts,
+  dur,
+  cpu,
+  power_per_thread.utid,
+  end_state,
+  priority,
+  power_ma,
+  power_per_thread.type,
+  name AS thread_name,
+  upid,
+  is_main_thread
+FROM power_per_thread
+  JOIN chrome_thread
+WHERE power_per_thread.utid = chrome_thread.utid;
+
+DROP TABLE IF EXISTS {{input}}_power;
+CREATE VIRTUAL TABLE {{input}}_power USING SPAN_JOIN(
+  {{input}},
+  power_per_chrome_thread PARTITIONED utid
+);
+
+-- Estimated power usage for chrome across the categroy slices contained in
+-- input.
+DROP VIEW IF EXISTS {{output}};
+CREATE VIEW {{output}} AS
+SELECT id,
+  ts,
+  dur,
+  {{category}},
+  mas,
+  mas / dur * 1e9 AS ma
+FROM (
+    SELECT s.id,
+      s.ts,
+      s.dur,
+      s.{{category}},
+      SUM(r.power_ma * r.dur) / 1e9 AS mas
+    FROM {{input}}_power r
+      JOIN {{input}} s
+    WHERE r.id == s.id
+    GROUP BY s.id
+  );
diff --git a/src/trace_processor/metrics/chrome/estimated_power_by_rail_mode.sql b/src/trace_processor/metrics/chrome/estimated_power_by_rail_mode.sql
new file mode 100644
index 0000000..a0b9b22
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/estimated_power_by_rail_mode.sql
@@ -0,0 +1,26 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/rail_modes.sql');
+
+-- Creates a view called power_by_rail_mode, containing the estimated CPU power
+-- usage for chrome broken down by RAIL Mode.
+SELECT RUN_METRIC(
+    'chrome/estimated_power_by_category.sql',
+    'input', 'combined_overall_rail_slices',
+    'output', 'power_by_rail_mode',
+    'category', 'rail_mode'
+  );
diff --git a/src/trace_processor/metrics/chrome/rail_modes.sql b/src/trace_processor/metrics/chrome/rail_modes.sql
new file mode 100644
index 0000000..50f57a6
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/rail_modes.sql
@@ -0,0 +1,516 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/chrome_event_metadata.sql') AS suppress_query_output;
+
+-- Priority order for RAIL modes where response has the highest priority and
+-- idle has the lowest.
+CREATE TABLE IF NOT EXISTS rail_modes (
+  mode TEXT UNIQUE,
+  ordering INT,
+  short_name TEXT
+);
+
+-- RAIL_MODE_IDLE is used when no frames are visible in the renderer and so this
+-- interprets that as background.
+-- RAIL_MODE_LOAD is for the time from a navigation until the first meaningful
+-- paint (assuming there are no user interactions).
+-- RAIL_MODE_RESPONSE is used when the main thread is dealing with a
+-- user-interaction (but not for instance for scrolls which may be handled by
+-- the compositor).
+-- RAIL_MODE_ANIMATION is used when none of the above apply.
+-- The enum in chrome is defined in:
+-- https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/scheduler/public/rail_mode_observer.h
+INSERT
+  OR IGNORE INTO rail_modes
+VALUES ('RAIL_MODE_IDLE', 0, 'background'),
+  ('RAIL_MODE_ANIMATION', 1, "animation"),
+  ('RAIL_MODE_LOAD', 2, "load"),
+  ('RAIL_MODE_RESPONSE', 3, "response");
+
+-- View containing all Scheduler.RAILMode slices across all Chrome renderer
+-- processes.
+DROP VIEW IF EXISTS original_rail_mode_slices;
+CREATE VIEW original_rail_mode_slices AS
+SELECT slice.id,
+  ts,
+  CASE
+    WHEN dur == -1 THEN trace_bounds.end_ts - ts
+    ELSE dur
+  END AS dur,
+  track_id,
+  EXTRACT_ARG(slice.arg_set_id, "chrome_renderer_scheduler_state.rail_mode") AS rail_mode
+FROM trace_bounds,
+  slice
+WHERE slice.name = "Scheduler.RAILMode";
+
+-- RAIL_MODE_LOAD seems to get stuck which makes it not very useful so remap it
+-- to RAIL_MODE_ANIMATION so it doesn't dominate the overall RAIL mode.
+DROP VIEW IF EXISTS rail_mode_slices;
+CREATE VIEW rail_mode_slices AS
+SELECT ts, dur, track_id,
+    CASE
+      WHEN rail_mode == "RAIL_MODE_LOAD" THEN "RAIL_MODE_ANIMATION"
+      ELSE rail_mode
+    END AS rail_mode
+FROM original_rail_mode_slices;
+
+-- View containing a collapsed view of rail_mode_slices where there is only one
+-- RAIL mode active at a given time. The mode is derived using the priority
+-- order in rail_modes.
+DROP VIEW IF EXISTS overall_rail_mode_slices;
+CREATE VIEW overall_rail_mode_slices AS
+SELECT s.ts,
+  s.end_ts,
+  rail_modes.short_name AS rail_mode,
+  MAX(rail_modes.ordering)
+FROM (
+    SELECT ts,
+      LEAD(ts, 1, trace_bounds.end_ts) OVER (
+        ORDER BY ts
+      ) AS end_ts
+    FROM (
+        SELECT DISTINCT ts
+        FROM rail_mode_slices
+      ) start_times,
+      trace_bounds
+  ) s,
+  rail_mode_slices r,
+  rail_modes
+WHERE (
+    (
+      s.ts >= r.ts AND s.ts < r.ts + r.dur
+    )
+    OR (
+      s.end_ts > r.ts AND s.end_ts <= r.ts + r.dur
+    )
+  )
+  AND r.rail_mode == rail_modes.mode
+GROUP BY s.ts;
+
+-- Contains the same data as overall_rail_mode_slices except adjacent slices
+-- with the same RAIL mode are combined.
+CREATE TABLE IF NOT EXISTS combined_overall_rail_slices AS
+SELECT ROW_NUMBER() OVER () AS id,
+  ts,
+  end_ts - ts AS dur,
+  rail_mode
+FROM (
+    SELECT lag(l.end_ts, 1, FIRST) OVER (
+        ORDER BY l.ts
+      ) AS ts,
+      l.end_ts,
+      l.rail_mode
+    FROM (
+        SELECT ts,
+          end_ts,
+          rail_mode
+        FROM overall_rail_mode_slices s
+        WHERE NOT EXISTS (
+            SELECT NULL
+            FROM overall_rail_mode_slices s2
+            WHERE s.rail_mode = s2.rail_mode
+              AND s.end_ts = s2.ts
+          )
+      ) AS l,
+      (
+        SELECT min(ts) AS FIRST
+        FROM overall_rail_mode_slices
+      )
+  );
+
+-- Now we have the RAIL Mode, use other trace events to create a modified RAIL
+-- mode that more accurately reflects what the browser/user are doing.
+
+-- First create slices for when there's no animation as indicated by a large gap
+-- between vsync events (since it's easier to find gaps than runs of adjacent
+-- vsyncs).
+
+-- Mark any large gaps between vsyncs.
+-- The value in "not_animating" is always 1. It's just there to be a non-NULL
+-- value so the later SPAN_JOIN can find the set-difference.
+DROP VIEW IF EXISTS not_animating_slices;
+CREATE VIEW not_animating_slices AS
+WITH const (vsync_padding, large_gap) AS (
+  SELECT
+    -- Pad 50ms either side of a vsync
+    50000000,
+    -- A gap of >200ms between the adjacent vsyncs is treated as a gap in
+    -- animation.
+    200000000
+)
+SELECT ts + const.vsync_padding AS ts,
+  gap_to_next_vsync - const.vsync_padding * 2 AS dur, 1 AS not_animating
+FROM const, (SELECT name,
+    ts,
+    lead(ts) OVER () - ts AS gap_to_next_vsync,
+    dur
+  FROM slice
+  WHERE name = "VSync")
+WHERE gap_to_next_vsync > const.large_gap
+UNION
+-- Insert a slice between start_ts and the first vsync (or the end of the trace
+-- if there are none).
+SELECT
+  ts,
+  dur,
+  1
+FROM (SELECT start_ts AS ts,
+          COALESCE((
+              SELECT MIN(ts)
+              FROM slice
+              WHERE name = "VSync"
+            ) - start_ts - const.vsync_padding,
+            end_ts - start_ts
+          ) AS dur
+FROM trace_bounds, const)
+WHERE dur > 0
+UNION
+-- Insert a slice between the last vsync and end_ts
+SELECT last_vsync AS ts,
+  end_ts - last_vsync AS dur,
+  1
+FROM (
+    SELECT MAX(ts) + const.vsync_padding AS last_vsync
+    FROM slice, const
+    WHERE name = "VSync"
+  ),
+  trace_bounds
+WHERE last_vsync < end_ts;
+
+-- There are two types of InputLatency:: events:
+-- 1) Simple ones that begin at ts and end at ts+dur
+-- 2) Paired ones that begin with a "begin" slice and end at an "end" slice.
+--
+-- Paired events are even trickier because we can't guarantee that the "begin"
+-- slice will even be in the trace and because it's possible for multiple begin
+-- slices to appear without an intervening end slice.
+
+-- Table of begin and end events along with the increment/decrement to be
+-- applied to the appropriate counter (one for each type of paired event). Final
+-- column dur_multiplier is used to find the timestamp to mark the event at in
+-- the equation event_ts = ts + dur * dur_multiplier. End events have
+-- dur_multiplier of 1, which makes their ts the end of the slice rather than
+-- the start.
+CREATE TABLE IF NOT EXISTS input_latency_begin_end_names
+(
+  full_name TEXT UNIQUE,
+  prefix TEXT,
+  scroll_increment INT,
+  pinch_increment INT,
+  touch_increment INT,
+  fling_increment INT,
+  pointer_increment INT,
+  dur_multiplier INT
+);
+
+INSERT
+  OR IGNORE INTO input_latency_begin_end_names
+VALUES
+  ("InputLatency::GestureScrollBegin",
+     "InputLatency::GestureScroll", 1, 0, 0, 0, 0, 0),
+  ("InputLatency::GestureScrollEnd",
+     "InputLatency::GestureScroll", -1, 0, 0, 0, 0, 1),
+  ("InputLatency::GesturePinchBegin",
+     "InputLatency::GesturePinch", 0, 1, 0, 0, 0, 0),
+  ("InputLatency::GesturePinchEnd",
+     "InputLatency::GesturePinch", 0, -1, 0, 0, 0, 1),
+  ("InputLatency::TouchStart",
+     "InputLatency::Touch", 0, 0, 1, 0, 0, 0),
+  ("InputLatency::TouchEnd",
+     "InputLatency::Touch", 0, 0, -1, 0, 0, 1),
+  ("InputLatency::GestureFlingStart",
+     "InputLatency::GestureFling", 0, 0, 0, 1, 0, 0),
+  ("InputLatency::GestureFlingCancel",
+     "InputLatency::GestureFling", 0, 0, 0, -1, 0, 1),
+  ("InputLatency::PointerDown",
+     "InputLatency::Pointer", 0, 0, 0, 0, 1, 0),
+  ("InputLatency::PointerUp",
+     "InputLatency::Pointer", 0, 0, 0, 0, -1, 1),
+  ("InputLatency::PointerCancel",
+     "InputLatency::Pointer", 0, 0, 0, 0, -1, 1);
+
+-- Find all the slices that have split "begin" and "end" slices and maintain a
+-- running total for each type, where >0 means that type of input event is
+-- ongoing.
+DROP VIEW IF EXISTS input_begin_end_slices;
+CREATE VIEW input_begin_end_slices AS
+SELECT prefix,
+  -- Mark the change at the start of "start" slices and the end of "end" slices.
+  ts + dur * dur_multiplier AS ts,
+  scroll_increment,
+  pinch_increment,
+  touch_increment,
+  fling_increment,
+  pointer_increment
+FROM slice
+JOIN input_latency_begin_end_names ON name = full_name
+ORDER BY ts;
+
+-- Combine all the paired input events to get an indication of when any paired
+-- input event is ongoing.
+DROP VIEW IF EXISTS unified_input_pair_increments;
+CREATE VIEW unified_input_pair_increments AS
+SELECT ts,
+  scroll_increment +
+  pinch_increment +
+  touch_increment +
+  fling_increment +
+  pointer_increment AS increment
+FROM input_begin_end_slices;
+
+-- It's possible there's an end slice without a start slice (as it occurred
+-- before the trace started) which would result in (starts - ends) going
+-- negative at some point. So find an offset that shifts up all counts so the
+-- lowest values becomes zero. It's possible this could still do the wrong thing
+-- if there were start AND end slices that are outside the trace bounds, in
+-- which case it should count as covering the entire trace, but it's impossible
+-- to compensate for that without augmenting the trace events themselves.
+DROP VIEW IF EXISTS initial_paired_increment;
+CREATE VIEW initial_paired_increment AS
+SELECT ts,
+  MIN(0, MIN(scroll_total)) +
+  MIN(0, MIN(pinch_total))  +
+  MIN(0, MIN(touch_total))  +
+  MIN(0, MIN(fling_total))  +
+  MIN(0, MIN(pointer_total)) AS offset
+FROM (
+    SELECT ts,
+      SUM(scroll_increment) OVER(ROWS UNBOUNDED PRECEDING) AS scroll_total,
+      SUM(pinch_increment)  OVER(ROWS UNBOUNDED PRECEDING) AS pinch_total,
+      SUM(touch_increment)  OVER(ROWS UNBOUNDED PRECEDING) AS touch_total,
+      SUM(fling_increment)  OVER(ROWS UNBOUNDED PRECEDING) AS fling_total,
+      SUM(pointer_increment)  OVER(ROWS UNBOUNDED PRECEDING) AS pointer_total
+    FROM input_begin_end_slices
+  );
+
+-- Now find all the simple input slices that fully enclose the input they're
+-- marking (i.e. not the start or end of a pair).
+DROP VIEW IF EXISTS simple_input_slices;
+CREATE VIEW simple_input_slices AS
+SELECT id,
+  name,
+  ts,
+  dur
+FROM slice s
+WHERE name LIKE "InputLatency::%"
+  AND NOT EXISTS (
+    SELECT 1
+    FROM slice
+      JOIN input_latency_begin_end_names
+    WHERE s.name == full_name
+  );
+
+-- Turn the simple input slices into +1s and -1s at the start and end of each
+-- slice.
+DROP VIEW IF EXISTS simple_input_increments;
+CREATE VIEW simple_input_increments AS
+SELECT ts,
+  1 AS increment
+FROM simple_input_slices
+UNION ALL
+SELECT ts + dur,
+  -1
+FROM simple_input_slices
+ORDER BY ts;
+
+-- Combine simple and paired inputs into one, summing all the increments at a
+-- given ts.
+DROP VIEW IF EXISTS all_input_increments;
+CREATE VIEW all_input_increments AS
+SELECT ts,
+  SUM(increment) AS increment
+FROM (
+    SELECT *
+    FROM simple_input_increments
+    UNION ALL
+    SELECT *
+    FROM unified_input_pair_increments
+    ORDER BY ts
+  )
+GROUP BY ts;
+
+-- Now calculate the cumulative sum of the increments as each ts, giving the
+-- total number of outstanding input events at a given time.
+DROP VIEW IF EXISTS all_input_totals;
+CREATE VIEW all_input_totals AS
+SELECT ts,
+  SUM(increment) OVER(ROWS UNBOUNDED PRECEDING) > 0 AS input_total
+FROM all_input_increments;
+
+-- Now find the transitions from and to 0 and use that to create slices where
+-- input events were occurring. The input_active column always contains 1, but
+-- is there so that the SPAN_JOIN_LEFT can put NULL in it for RAIL Mode slices
+-- that do not have corresponding input events.
+DROP VIEW IF EXISTS all_input_slices;
+CREATE VIEW all_input_slices AS
+SELECT ts,
+  dur,
+  input_active
+FROM (
+    SELECT ts,
+      lead(ts, 1, end_ts) OVER() - ts AS dur,
+      input_active
+    FROM trace_bounds,
+      (
+        SELECT ts,
+          input_total > 0 AS input_active
+        FROM (
+            SELECT ts,
+              input_total,
+              lag(input_total) OVER() AS prev_input_total
+            FROM all_input_totals
+          )
+        WHERE (input_total > 0 <> prev_input_total > 0)
+          OR prev_input_total IS NULL
+      )
+  )
+WHERE input_active > 0;
+
+-- Since the scheduler defaults to animation when none of the other RAIL modes
+-- apply, animation overestimates the amount of time that actual animation is
+-- occurring.
+-- So instead we try to divide up animation in other buckets based on other
+-- trace events.
+DROP VIEW IF EXISTS rail_mode_animation_slices;
+CREATE VIEW rail_mode_animation_slices AS
+SELECT * FROM combined_overall_rail_slices WHERE rail_mode = "animation";
+
+-- Left-join rail mode animation slices with all_input_slices to find all
+-- "animation" slices that should actually be labelled "response".
+DROP TABLE IF EXISTS rail_mode_join_inputs;
+CREATE VIRTUAL TABLE rail_mode_join_inputs
+USING SPAN_LEFT_JOIN(rail_mode_animation_slices, all_input_slices);
+
+-- Left-join rail mode animation slices with not_animating_slices which is
+-- based on the gaps between vsync events.
+DROP TABLE IF EXISTS rail_mode_join_inputs_join_animation;
+CREATE VIRTUAL TABLE rail_mode_join_inputs_join_animation
+USING SPAN_LEFT_JOIN(rail_mode_join_inputs, not_animating_slices);
+
+DROP VIEW IF EXISTS has_modified_rail_slices;
+CREATE VIEW has_modified_rail_slices AS
+SELECT (
+    SELECT value
+    FROM chrome_event_metadata
+    WHERE name == "os-name"
+  ) == "Android" AS value;
+
+-- Mapping to allow CamelCased names to be produced from the modified rail
+-- modes.
+CREATE TABLE IF NOT EXISTS modified_rail_mode_prettier (
+  orig_name TEXT UNIQUE,
+  pretty_name TEXT
+);
+INSERT
+  OR IGNORE INTO modified_rail_mode_prettier
+VALUES ("background", "Background"),
+  ("foreground_idle", "ForegroundIdle"),
+  ("animation", "Animation"),
+  ("load", "Load"),
+  ("response", "Response");
+
+-- When the RAIL mode is animation, use input/vsync data to conditionally change
+-- the mode to response or foreground_idle.
+DROP VIEW IF EXISTS unmerged_modified_rail_slices;
+CREATE VIEW unmerged_modified_rail_slices AS
+SELECT ROW_NUMBER() OVER () AS id,
+  ts,
+  dur,
+  mode
+FROM (
+    SELECT ts,
+      dur,
+      CASE
+        WHEN input_active IS NOT NULL THEN "response"
+        WHEN not_animating IS NULL THEN "animation"
+        ELSE "foreground_idle"
+      END AS mode
+    FROM rail_mode_join_inputs_join_animation
+    UNION
+    SELECT ts,
+      dur,
+      rail_mode AS mode
+    FROM combined_overall_rail_slices
+    WHERE rail_mode <> "animation"
+  )
+  -- Since VSync events are only emitted on Android (and the concept of a
+  -- unified RAIL mode only makes sense if there's just a single Chrome window),
+  -- don't output anything on other platforms. This will result in all the power
+  -- and cpu time tables being empty rather than containing bogus results.
+WHERE (
+    SELECT value
+    FROM has_modified_rail_slices
+  );
+
+-- The previous query creating unmerged_modified_rail_slices, can create
+-- adjacent slices with the same mode. This merges them together as well as
+-- adding a unique id to each slice. Rather than directly merging slices
+-- together, this instead looks for all the transitions and uses this to
+-- reconstruct the slices that should occur between them.
+DROP VIEW IF EXISTS modified_rail_slices;
+CREATE VIEW modified_rail_slices AS
+WITH const (end_ts) AS (SELECT ts + dur
+              FROM unmerged_modified_rail_slices
+              ORDER BY ts DESC
+              LIMIT 1)
+SELECT ROW_NUMBER() OVER () AS id, lag(next_ts) OVER() AS ts,
+  ts + dur - lag(next_ts) OVER() AS dur,
+  mode AS mode
+FROM (
+    -- For each row in the original table, create a new row with the information
+    -- from the following row, since you can't use lag/lead in WHERE clause.
+    --
+    -- Transition row at the beginning. "mode" is invalid, so a transition will
+    -- always be recorded.
+    SELECT *
+    FROM (SELECT
+          0 AS ts,
+          ts AS dur,
+          "" AS mode,
+          ts AS next_ts,
+          dur AS next_dur,
+          mode AS next_mode
+        FROM unmerged_modified_rail_slices
+        LIMIT 1
+      )
+    UNION ALL
+    SELECT ts,
+      dur,
+      mode,
+      lead(ts, 1, end_ts) OVER() AS next_ts,
+      lead(dur) OVER() AS next_dur,
+      lead(mode) OVER() AS next_mode
+    FROM unmerged_modified_rail_slices, const
+    UNION ALL
+    -- Transition row at the end. "next_mode" is invalid, so a transition will
+    -- always be recorded.
+    SELECT *
+    FROM (SELECT
+          ts + dur AS ts,
+          0 AS dur,
+          mode,
+          ts + dur AS next_ts,
+          0,
+          "" AS next_mode
+        FROM unmerged_modified_rail_slices
+        ORDER BY ts DESC
+        LIMIT 1
+      )
+  )
+WHERE mode <> next_mode
+-- Retrieve all but the first row.
+LIMIT -1 OFFSET 1;
diff --git a/src/trace_processor/metrics/chrome/scroll_jank_cause.sql b/src/trace_processor/metrics/chrome/scroll_jank_cause.sql
index 54aa922..e955bdf 100644
--- a/src/trace_processor/metrics/chrome/scroll_jank_cause.sql
+++ b/src/trace_processor/metrics/chrome/scroll_jank_cause.sql
@@ -31,6 +31,7 @@
 -- 5) modify the scroll_jank_cause_explained_jank to include your cause.
 SELECT RUN_METRIC('chrome/scroll_jank_cause_blocking_touch_move.sql');
 SELECT RUN_METRIC('chrome/scroll_jank_cause_blocking_task.sql');
+SELECT RUN_METRIC('chrome/scroll_jank_cause_get_bitmap.sql');
 
 DROP VIEW IF EXISTS scroll_jank_cause_joined;
 
@@ -40,13 +41,19 @@
     COALESCE(task.blocked_by_language_detection, 0)
         AS blocked_by_language_detection,
     COALESCE(task.blocked_by_copy_request, 0) AS blocked_by_copy_request,
+    COALESCE(bitmap.blocked_by_bitmap, 0) AS blocked_by_bitmap,
+    COALESCE(bitmap.blocked_by_toolbar, 0) AS blocked_by_toolbar,
+    COALESCE(bitmap.blocked_by_bitmap_no_toolbar, 0)
+        AS blocked_by_bitmap_no_toolbar,
     jank.*
   FROM
     scroll_jank jank LEFT JOIN
     scroll_jank_cause_blocking_touch_move move
         ON jank.id = move.scroll_id LEFT JOIN
     scroll_jank_cause_blocking_task task
-        ON jank.id = task.scroll_id;
+        ON jank.id = task.scroll_id LEFT JOIN
+    scroll_jank_cause_get_bitmap bitmap
+        ON jank.id = bitmap.scroll_id;
 
 DROP VIEW IF EXISTS scroll_jank_cause_explained_jank;
 
@@ -60,7 +67,8 @@
       CASE WHEN
         blocking_touch_move OR
         blocked_by_language_detection OR
-        blocked_by_copy_request
+        blocked_by_copy_request OR
+        blocked_by_bitmap
       THEN
         TRUE
       ELSE
@@ -74,6 +82,6 @@
 
 CREATE VIEW scroll_jank_cause AS
   SELECT
-    NOT explained_jank AS unexplained_jank,
+    jank AND NOT explained_jank AS unexplained_jank,
     jank.*
   FROM scroll_jank_cause_explained_jank jank;
diff --git a/src/trace_processor/metrics/chrome/scroll_jank_cause_get_bitmap.sql b/src/trace_processor/metrics/chrome/scroll_jank_cause_get_bitmap.sql
new file mode 100644
index 0000000..af4cff5
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/scroll_jank_cause_get_bitmap.sql
@@ -0,0 +1,195 @@
+--
+-- Copyright 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
+--
+--     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.
+
+-- Needed for the scroll_jank table to tell which updates were janky.
+SELECT RUN_METRIC('chrome/scroll_jank.sql');
+
+--------------------------------------------------------------------------------
+-- Get all the track ids relevant to the critical path.
+--------------------------------------------------------------------------------
+
+-- Grab the track of the browser. sendTouchEvent is a Java category event which
+-- only occurs on the browser. This saves us the trouble of dealing with all the
+-- different possible names of the browser (when including system tracing).
+DROP VIEW IF EXISTS browser_main_track_id;
+
+CREATE VIEW browser_main_track_id AS
+SELECT
+  track_id AS id
+FROM slice
+WHERE
+  name = "sendTouchEvent"
+LIMIT 1;
+
+--------------------------------------------------------------------------------
+-- Grab the last LatencyInfo.Flow for each trace_id on the browser main.
+--------------------------------------------------------------------------------
+DROP VIEW IF EXISTS browser_flows;
+
+CREATE VIEW browser_flows AS
+SELECT
+  EXTRACT_ARG(arg_set_id, "chrome_latency_info.trace_id") AS trace_id,
+  EXTRACT_ARG(arg_set_id, "chrome_latency_info.step") AS flow_step,
+  track_id,
+  max(ts) AS ts
+FROM slice
+WHERE
+  track_id = (
+    SELECT id FROM browser_main_track_id
+  ) AND
+  name = "LatencyInfo.Flow"
+  GROUP BY trace_id;
+
+--------------------------------------------------------------------------------
+-- Join the relevant tracks/flows to the individual scrolls.
+--------------------------------------------------------------------------------
+
+-- Keeping only the GestureScrollUpdates join the maximum flows on the browser
+-- thread. 
+DROP VIEW IF EXISTS scroll_with_browser_flows;
+
+CREATE VIEW scroll_with_browser_flows AS
+SELECT
+  scroll.trace_id,
+  scroll.scroll_id,
+  scroll.ts,
+  scroll.dur,
+  scroll.track_id,
+  browser_flows.ts AS browser_flow_ts,
+  browser_flows.flow_step AS browser_flow_step,
+  browser_flows.track_id AS browser_track_id
+FROM (
+  SELECT
+    trace_id,
+    id AS scroll_id,
+    ts,
+    dur,
+    track_id
+  FROM scroll_jank
+) scroll JOIN browser_flows ON
+    scroll.trace_id = browser_flows.trace_id;
+
+--------------------------------------------------------------------------------
+-- Below we determine if there was any bitmaps taken on the browser main.
+--------------------------------------------------------------------------------
+DROP VIEW IF EXISTS get_bitmap_calls;
+
+CREATE VIEW get_bitmap_calls AS
+  SELECT
+    id,
+    ts,
+    dur,
+    track_id
+  FROM slice
+  WHERE
+    slice.name = "ViewResourceAdapter:getBitmap" AND
+    track_id = (SELECT id FROM browser_main_track_id);
+
+DROP VIEW IF EXISTS toolbar_bitmaps;
+
+CREATE VIEW toolbar_bitmaps AS
+  SELECT
+    slice.id,
+    slice.ts,
+    slice.dur,
+    slice.track_id,
+    ancestor.id AS ancestor_id
+  FROM
+    slice JOIN
+    ancestor_slice(slice.id) AS ancestor ON
+      ancestor.depth = slice.depth - 1
+  WHERE
+    slice.name = "ToolbarLayout.draw" AND
+    ancestor.name = "ViewResourceAdapter:getBitmap" AND
+    slice.track_id = (SELECT id FROM browser_main_track_id);
+
+DROP VIEW IF EXISTS get_bitmaps_and_toolbar;
+
+CREATE VIEW get_bitmaps_and_toolbar AS
+  SELECT
+    bitmap.id AS id,
+    bitmap.ts AS ts,
+    bitmap.dur AS dur,
+    bitmap.track_id AS track_id,
+    toolbar.id AS toolbar_id,
+    toolbar.ts AS toolbar_ts,
+    toolbar.dur AS toolbar_dur,
+    toolbar.track_id AS toolbar_track_id
+  FROM
+    get_bitmap_calls bitmap LEFT JOIN
+    toolbar_bitmaps toolbar ON
+      toolbar.ancestor_id = bitmap.id;
+
+--------------------------------------------------------------------------------
+-- Take bitmaps and determine if it could have been blocked by a scroll. I.E. if
+-- the bitmap occurred after the start of the GestureScrollUpdate but before the
+-- last flow on the browser thread (the GestureScrollUpdate can't be blocked
+-- by a browser thread slice once its done on the browser thread).
+--------------------------------------------------------------------------------
+DROP VIEW IF EXISTS blocking_bitmap_tasks;
+
+CREATE VIEW blocking_bitmap_tasks AS
+SELECT
+  scroll.scroll_id,
+  scroll.trace_id,
+  bitmap.id,
+  bitmap.ts,
+  bitmap.dur,
+  bitmap.track_id,
+  CASE WHEN
+      bitmap.track_id = scroll.browser_track_id AND
+      bitmap.ts < scroll.browser_flow_ts THEN
+    TRUE
+  ELSE
+    FALSE
+  END AS blocked_by_bitmap,
+  CASE WHEN
+      bitmap.track_id = scroll.browser_track_id AND
+      bitmap.toolbar_id IS NOT NULL AND
+      bitmap.ts < scroll.browser_flow_ts THEN
+    TRUE
+  ELSE
+    FALSE
+  END AS blocked_by_toolbar,
+  CASE WHEN
+      bitmap.track_id = scroll.browser_track_id AND
+      bitmap.toolbar_id IS NULL AND
+      bitmap.ts < scroll.browser_flow_ts THEN
+    TRUE
+  ELSE
+    FALSE
+  END AS blocked_by_bitmap_no_toolbar
+FROM
+  scroll_with_browser_flows scroll JOIN
+  get_bitmaps_and_toolbar bitmap ON
+  scroll.ts + scroll.dur >= bitmap.ts AND
+  bitmap.ts + bitmap.dur >= scroll.ts;
+
+
+--------------------------------------------------------------------------------
+-- Remove duplicate tasks blocking so that there is only a boolean per
+-- scroll_id.
+--------------------------------------------------------------------------------
+DROP VIEW IF EXISTS scroll_jank_cause_get_bitmap;
+
+CREATE VIEW scroll_jank_cause_get_bitmap AS
+SELECT
+  scroll_id,
+  trace_id,
+  SUM(blocked_by_bitmap) > 0 AS blocked_by_bitmap,
+  SUM(blocked_by_toolbar) > 0 AS blocked_by_toolbar,
+  SUM(blocked_by_bitmap_no_toolbar) > 0 AS blocked_by_bitmap_no_toolbar
+FROM blocking_bitmap_tasks
+GROUP BY 1, 2;
diff --git a/src/trace_processor/metrics/chrome/scroll_jank_cause_queuing_delay.sql b/src/trace_processor/metrics/chrome/scroll_jank_cause_queuing_delay.sql
new file mode 100644
index 0000000..237da55
--- /dev/null
+++ b/src/trace_processor/metrics/chrome/scroll_jank_cause_queuing_delay.sql
@@ -0,0 +1,279 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/chrome_thread_slice_with_cpu_time.sql');
+SELECT RUN_METRIC('chrome/scroll_flow_event_queuing_delay.sql');
+
+
+-- This view grabs any slice that could have prevented any GestureScrollUpdate
+-- flow event from being run (queuing delays). For RunTask we know that its
+-- generic (and thus hard to figure out whats the cause) so we grab the src
+-- location to make it more meaningful.
+--
+-- See b/166441398 & crbug/1094361 for why we remove the -to-End step. In
+-- essence -to-End is often reported on the ThreadPool after the fact with
+-- explicit timestamps so it being blocked isn't noteworthy.
+DROP TABLE IF EXISTS blocking_tasks_queuing_delay;
+
+CREATE TABLE blocking_tasks_queuing_delay AS
+  SELECT
+    EXTRACT_ARG(arg_set_id, "task.posted_from.file_name") as file,
+    EXTRACT_ARG(arg_set_id, "task.posted_from.function_name") as function,
+    trace_id,
+    queuing_time_ns,
+    next_track_id,
+    CASE WHEN queuing.ancestor_end <= slice.ts THEN
+      CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN
+        slice.dur
+      ELSE
+        queuing.maybe_next_ancestor_ts - slice.ts
+      END
+    ELSE
+      CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN
+        slice.ts + slice.dur - queuing.ancestor_end
+      ELSE
+        queuing.maybe_next_ancestor_ts - queuing.ancestor_end
+      END
+    END AS dur_overlapping_ns,
+    description,
+    scroll_slice_id,
+    scroll_ts,
+    scroll_dur,
+    scroll_track_id,
+    jank,
+    slice.*
+  FROM
+    scroll_flow_event_queuing_delay queuing JOIN
+    chrome_thread_slice_with_cpu_time slice ON
+        slice.ts + slice.dur > queuing.ancestor_end AND
+        queuing.maybe_next_ancestor_ts > slice.ts AND
+        slice.track_id = queuing.next_track_id AND
+        slice.depth = 0 AND
+        queuing.description NOT LIKE
+            "InputLatency.LatencyInfo.%ank.STEP_DRAW_AND_SWAP-to-End"
+  WHERE
+    queuing_time_ns IS NOT NULL AND
+    queuing_time_ns > 0;
+
+-- Now for each toplevel task (depth = 0 from above) we want to grab all their
+-- children slices. This is done by joining on descendant_slice which is a
+-- trace processor defined operator. This will results in 1 row for every
+-- descendant slice. So all fields in base.* will be repeated ONCE for each
+-- child, but if it has no slice it will occur only once but all the
+-- |descendant_.*| fields will be NULL because of the LEFT JOIN.
+DROP VIEW IF EXISTS all_descendant_blocking_tasks_queuing_delay;
+
+CREATE VIEW all_descendant_blocking_tasks_queuing_delay AS
+  SELECT
+    descendant.id AS descendant_id,
+    descendant.ts AS descendant_ts,
+    descendant.dur AS descendant_dur,
+    descendant.name AS descendant_name,
+    descendant.parent_id As descendant_parent_id,
+    descendant.depth AS descendant_depth,
+    base.*
+  FROM
+    blocking_tasks_queuing_delay base LEFT JOIN
+    descendant_slice(base.id) AS descendant;
+
+DROP TABLE IF EXISTS all_descendant_blocking_tasks_queuing_delay_with_cpu_time;
+
+CREATE TABLE all_descendant_blocking_tasks_queuing_delay_with_cpu_time AS
+  SELECT
+    cpu.slice_cpu_time AS descendant_slice_cpu_time,
+    cpu.slice_cpu_time / descendant.slice_cpu_time AS descendant_cpu_percentage,
+    cpu.slice_cpu_time /
+        (descendant.slice_cpu_time /
+          (1 << (descendant.descendant_depth - 1))) > 0.5
+            AS descendant_cpu_time_above_relative_threshold,
+    descendant_dur / descendant.dur AS descendant_dur_percentage,
+    descendant_dur /
+        (descendant.dur / (1 << (descendant.descendant_depth - 1))) > 0.5
+        AS descendant_dur_above_relative_threshold,
+    descendant.*
+  FROM
+    all_descendant_blocking_tasks_queuing_delay descendant LEFT JOIN (
+      SELECT
+        id, slice_cpu_time
+      FROM chrome_thread_slice_with_cpu_time
+    ) AS cpu ON
+        cpu.id = descendant.descendant_id;
+
+-- Now that we've generated the descendant count how many siblings each row
+-- has. Recall that all the top level tasks are repeated but each row represents
+-- a descendant slice. This means since we LEFT JOIN we will say a slice has 0
+-- siblings if it has no descendants (which is true), and otherwise we will
+-- compute the siblings as the count of all slices with the same parent minus
+-- the current slice.
+DROP VIEW IF EXISTS counted_descendant_blocking_tasks_queuing_delay;
+
+CREATE VIEW counted_descendant_blocking_tasks_queuing_delay AS
+  SELECT
+    base.*,
+    COALESCE(single_descendant.number_of_siblings, 0) AS number_of_siblings
+  FROM
+    all_descendant_blocking_tasks_queuing_delay_with_cpu_time base LEFT JOIN (
+      SELECT
+        descendant_parent_id,
+        COUNT(*) - 1 AS number_of_siblings
+      FROM all_descendant_blocking_tasks_queuing_delay_with_cpu_time
+      WHERE descendant_parent_id IS NOT NULL
+      GROUP BY 1
+  ) single_descendant ON
+      single_descendant.descendant_parent_id = base.descendant_parent_id;
+
+-- Now we group by the |id| which is the top level task id and find the first
+-- descendant_depth where we have a sibling. We need this because we only want
+-- to include single descendant slices in our metric name to keep it easy to
+-- reason about what that code is doing.
+DROP VIEW IF EXISTS blocking_tasks_queuing_delay_with_invalid_depth;
+
+CREATE VIEW blocking_tasks_queuing_delay_with_invalid_depth AS
+  SELECT
+    base.*,
+    (
+      descendant_cpu_time_above_relative_threshold AND
+      descendant_cpu_percentage > 0.05
+    ) OR (
+      descendant_dur_above_relative_threshold AND
+      descendant_dur_percentage > 0.05
+    ) AS descendant_major_slice,
+    COALESCE(depth.invalid_depth, 10) AS invalid_depth
+  FROM
+    counted_descendant_blocking_tasks_queuing_delay base LEFT JOIN (
+      SELECT
+        id,
+        MIN(descendant_depth) AS invalid_depth
+      FROM counted_descendant_blocking_tasks_queuing_delay
+      WHERE number_of_siblings >= 1
+      GROUP BY 1
+    ) AS depth ON base.id = depth.id
+  ORDER BY
+    descendant_depth ASC,
+    descendant_cpu_percentage DESC,
+    descendant_dur_percentage DESC;
+
+-- Now to get back to a single output per top level task we group by all the
+-- toplevel fields and aggregate the descendant fields. We only include the
+-- descendant if their depth is less than the first depth with siblings (the
+-- |invalid_depth|).
+DROP VIEW IF EXISTS descendant_blocking_tasks_queuing_delay;
+
+CREATE VIEW descendant_blocking_tasks_queuing_delay AS
+  SELECT
+    id,
+    ts,
+    dur,
+    track_id,
+    trace_id,
+    name,
+    scroll_slice_id AS scroll_id,
+    scroll_ts,
+    scroll_dur,
+    scroll_track_id,
+    jank,
+    queuing_time_ns,
+    dur_overlapping_ns,
+    description,
+    replace(file, rtrim(file, replace(file, '/', '')), '') AS file,
+    function,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_id
+      ELSE
+        NULL
+      END
+    , "-") AS descendant_id,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_ts
+      ELSE
+        NULL
+      END
+    , "-") AS descendant_ts,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_dur
+      ELSE
+        NULL
+      END
+    , "-") AS descendant_dur,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_name
+      ELSE
+        NULL
+      END, "-") AS descendant_name,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_slice_cpu_time
+      ELSE
+        NULL
+      END
+    , "-") AS descendant_slice_cpu_time,
+    GROUP_CONCAT(
+      CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN
+        descendant_cpu_percentage
+      ELSE
+        NULL
+      END
+    , "-") AS descendant_cpu_time
+  FROM
+    blocking_tasks_queuing_delay_with_invalid_depth
+  GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+  ORDER BY descendant_cpu_percentage DESC;
+
+-- Create a common name for each "cause" based on the slice stack we found.
+DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_temp;
+
+CREATE VIEW scroll_jank_cause_queuing_delay_temp AS
+  SELECT
+    CASE WHEN name = "ThreadControllerImpl::RunTask" THEN
+      'posted-from-' || function || '()-in-' || file
+    ELSE
+      name
+    END || COALESCE("-" || descendant_name, "") AS location,
+
+    base.*
+  FROM descendant_blocking_tasks_queuing_delay base;
+
+-- Figure out the average time taken during non-janky scrolls updates for each
+-- TraceEvent (metric_name) stack.
+DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_average_time;
+
+CREATE VIEW scroll_jank_cause_queuing_delay_average_no_jank_time AS
+  SELECT
+    location,
+    AVG(dur_overlapping_ns) as avg_dur_overlapping_ns
+  FROM scroll_jank_cause_queuing_delay_temp
+  WHERE NOT jank
+  GROUP BY 1;
+
+-- Join every row (jank and non-jank with the average non-jank time for the
+-- given metric_name).
+DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay;
+
+CREATE VIEW scroll_jank_cause_queuing_delay AS
+  SELECT
+    base.*,
+    'InputLatency.LatencyInfo.Flow.QueuingDelay.' ||
+    CASE WHEN jank THEN 'Jank' ELSE 'NoJank' END || '.BlockingTasksUs.' ||
+      base.location as metric_name,
+    COALESCE(avg_no_jank.avg_dur_overlapping_ns, 0)
+        AS avg_no_jank_dur_overlapping_ns
+  FROM
+    scroll_jank_cause_queuing_delay_temp base LEFT JOIN
+    scroll_jank_cause_queuing_delay_average_no_jank_time avg_no_jank ON
+        base.location = avg_no_jank.location;
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/src/trace_processor/metrics/chrome/test_chrome_metric.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to src/trace_processor/metrics/chrome/test_chrome_metric.sql
index cdd953a..79667fc 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/src/trace_processor/metrics/chrome/test_chrome_metric.sql
@@ -12,14 +12,6 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+CREATE VIEW test_chrome_metric_output AS
+SELECT TestChromeMetric('test_value', 1)
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index 99e243a..3e7ac8f 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -542,7 +542,7 @@
   // Even if the message is empty, we don't return null here as we want the
   // existence of the message to be respected.
   std::vector<uint8_t> raw = builder.SerializeToProtoBuilderResult();
-  if (raw.size() == 0) {
+  if (raw.empty()) {
     // Passing nullptr to SQLite feels dangerous so just pass an empty string
     // and zero as the size so we don't deref nullptr accidentially somewhere.
     sqlite3_result_blob(ctx, "", 0, nullptr);
@@ -588,8 +588,9 @@
     std::string buffer;
     int ret = TemplateReplace(query, substitutions, &buffer);
     if (ret) {
-      sqlite3_result_error(
-          ctx, "RUN_METRIC: Error when performing substitution", -1);
+      char* error = sqlite3_mprintf(
+          "RUN_METRIC: Error when performing substitutions: %s", query.c_str());
+      sqlite3_result_error(ctx, error, -1);
       return;
     }
 
diff --git a/src/trace_processor/metrics/metrics.descriptor.h b/src/trace_processor/metrics/metrics.descriptor.h
deleted file mode 100644
index 33bb13b..0000000
--- a/src/trace_processor/metrics/metrics.descriptor.h
+++ /dev/null
@@ -1,1628 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_METRICS_METRICS_DESCRIPTOR_H_
-#define SRC_TRACE_PROCESSOR_METRICS_METRICS_DESCRIPTOR_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-
-// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
-
-// SHA1(tools/gen_binary_descriptors)
-// 0d729b5e362e01367aa3c0fd528d3035c1580255
-// SHA1(protos/perfetto/metrics/metrics.proto)
-// f5156fc72421bae158c30ed65e3a322c276ae805
-
-// This is the proto Metrics encoded as a ProtoFileDescriptor to allow
-// for reflection without libprotobuf full/non-lite protos.
-
-namespace perfetto {
-
-constexpr std::array<uint8_t, 19042> kMetricsDescriptor{
-    {0x0a, 0xf3, 0x06, 0x0a, 0x31, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x62, 0x61, 0x74, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xac, 0x06, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42,
-     0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x12, 0x60, 0x0a, 0x10, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0f, 0x62,
-     0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
-     0x72, 0x73, 0x12, 0x66, 0x0a, 0x12, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x73,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74,
-     0x65, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x61,
-     0x74, 0x74, 0x65, 0x72, 0x79, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61,
-     0x74, 0x65, 0x73, 0x52, 0x11, 0x62, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79,
-     0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x73, 0x1a, 0xd2,
-     0x01, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69,
-     0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
-     0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x68, 0x61,
-     0x72, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f,
-     0x75, 0x61, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63,
-     0x68, 0x61, 0x72, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x55, 0x61, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x61, 0x70, 0x61, 0x63,
-     0x69, 0x74, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18,
-     0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, 0x63, 0x61, 0x70, 0x61, 0x63,
-     0x69, 0x74, 0x79, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x1d,
-     0x0a, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x61,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x63, 0x75, 0x72, 0x72,
-     0x65, 0x6e, 0x74, 0x55, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x72,
-     0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x76, 0x67, 0x5f, 0x75, 0x61, 0x18,
-     0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65,
-     0x6e, 0x74, 0x41, 0x76, 0x67, 0x55, 0x61, 0x1a, 0xf4, 0x02, 0x0a, 0x11,
-     0x42, 0x61, 0x74, 0x74, 0x65, 0x72, 0x79, 0x41, 0x67, 0x67, 0x72, 0x65,
-     0x67, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x6f, 0x74,
-     0x61, 0x6c, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x66,
-     0x66, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10,
-     0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f,
-     0x66, 0x66, 0x4e, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61,
-     0x6c, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f,
-     0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f,
-     0x74, 0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x6e, 0x4e,
-     0x73, 0x12, 0x2f, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x64, 0x6f, 0x7a, 0x65, 0x5f, 0x6e,
-     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74,
-     0x61, 0x6c, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x6f, 0x7a, 0x65,
-     0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
-     0x77, 0x61, 0x6b, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x73, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x57, 0x61, 0x6b, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x73, 0x12, 0x19,
-     0x0a, 0x08, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x4e,
-     0x73, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x66, 0x66, 0x5f, 0x6e, 0x73,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x73, 0x6c, 0x65, 0x65,
-     0x70, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x66, 0x66, 0x4e, 0x73,
-     0x12, 0x2b, 0x0a, 0x12, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x63,
-     0x72, 0x65, 0x65, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x6e, 0x73, 0x18, 0x07,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x53,
-     0x63, 0x72, 0x65, 0x65, 0x6e, 0x4f, 0x6e, 0x4e, 0x73, 0x12, 0x2f, 0x0a,
-     0x14, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65,
-     0x6e, 0x5f, 0x64, 0x6f, 0x7a, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x08, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x53, 0x63,
-     0x72, 0x65, 0x65, 0x6e, 0x44, 0x6f, 0x7a, 0x65, 0x4e, 0x73, 0x0a, 0xd4,
-     0x08, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x63,
-     0x70, 0x75, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x8e, 0x08, 0x0a,
-     0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x4c, 0x0a, 0x0c, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x0b,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x1a,
-     0xa8, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12,
-     0x18, 0x0a, 0x07, 0x6d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x63, 0x79, 0x63, 0x6c, 0x65,
-     0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65,
-     0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72,
-     0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x20, 0x0a, 0x0c,
-     0x6d, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x46,
-     0x72, 0x65, 0x71, 0x4b, 0x68, 0x7a, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61,
-     0x78, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65,
-     0x71, 0x4b, 0x68, 0x7a, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x76, 0x67, 0x5f,
-     0x66, 0x72, 0x65, 0x71, 0x5f, 0x6b, 0x68, 0x7a, 0x18, 0x05, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0a, 0x61, 0x76, 0x67, 0x46, 0x72, 0x65, 0x71, 0x4b,
-     0x68, 0x7a, 0x1a, 0x65, 0x0a, 0x08, 0x43, 0x6f, 0x72, 0x65, 0x44, 0x61,
-     0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x07, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x06, 0x1a, 0x67, 0x0a,
-     0x0c, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74,
-     0x61, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a,
-     0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xf4, 0x01, 0x0a, 0x06, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-     0x65, 0x12, 0x43, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x3e,
-     0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x43, 0x6f, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x63, 0x6f,
-     0x72, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x74,
-     0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43,
-     0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x72,
-     0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x08, 0x63,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10,
-     0x04, 0x1a, 0xb9, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a,
-     0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x07, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x42, 0x0a, 0x07, 0x74, 0x68,
-     0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x28, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x52, 0x07, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x73, 0x12, 0x3e, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x07,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61,
-     0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x63, 0x6f,
-     0x72, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x44, 0x61, 0x74,
-     0x61, 0x52, 0x08, 0x63, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x4a,
-     0x04, 0x08, 0x03, 0x10, 0x04, 0x0a, 0xd0, 0x01, 0x0a, 0x35, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
-     0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x85, 0x01, 0x0a,
-     0x15, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x44, 0x69, 0x73, 0x70,
-     0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x34,
-     0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, 0x70, 0x6c,
-     0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61,
-     0x6c, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x64, 0x75, 0x70, 0x6c,
-     0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x5f, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x15, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64,
-     0x0a, 0xc0, 0x03, 0x0a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74,
-     0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf4, 0x02, 0x0a, 0x16, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x49, 0x0a,
-     0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x61, 0x63, 0x6b,
-     0x61, 0x67, 0x65, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x12, 0x59, 0x0a, 0x10, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73,
-     0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x08, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x61, 0x63, 0x6b,
-     0x61, 0x67, 0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x73, 0x46, 0x6f, 0x72, 0x55, 0x69, 0x64, 0x1a, 0x76, 0x0a, 0x07, 0x50,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x70, 0x6b,
-     0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x61, 0x70, 0x6b,
-     0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12,
-     0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c,
-     0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x65, 0x62,
-     0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10,
-     0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10,
-     0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x0a, 0xeb, 0x08, 0x0a, 0x3c,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x68, 0x65, 0x61, 0x70,
-     0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c,
-     0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x22, 0xe1, 0x07, 0x0a, 0x14, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72,
-     0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x12, 0x5a, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
-     0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61,
-     0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c,
-     0x73, 0x69, 0x74, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
-     0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e, 0x73,
-     0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x3e,
-     0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-     0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x70, 0x69,
-     0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x0b, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x4e, 0x61,
-     0x6d, 0x65, 0x1a, 0x8e, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79,
-     0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74,
-     0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1f, 0x0a,
-     0x0b, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x74,
-     0x61, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65,
-     0x6c, 0x74, 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x42, 0x79,
-     0x74, 0x65, 0x73, 0x1a, 0xa6, 0x02, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c,
-     0x73, 0x69, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,
-     0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68,
-     0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x70,
-     0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x41, 0x0a,
-     0x05, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50,
-     0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69,
-     0x74, 0x65, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x05, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x12, 0x4f, 0x0a, 0x0b, 0x73, 0x65, 0x6c, 0x66,
-     0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70,
-     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
-     0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-     0x73, 0x52, 0x0a, 0x73, 0x65, 0x6c, 0x66, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
-     0x73, 0x12, 0x51, 0x0a, 0x0c, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x61,
-     0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72,
-     0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
-     0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x73,
-     0x1a, 0xf2, 0x02, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69,
-     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64,
-     0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f,
-     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12,
-     0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x06,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x09, 0x63, 0x61,
-     0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70,
-     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
-     0x69, 0x74, 0x65, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x52, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73,
-     0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18,
-     0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18,
-     0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x3b, 0x0a, 0x1b, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6e, 0x6f, 0x6e,
-     0x5f, 0x72, 0x73, 0x73, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61,
-     0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x16, 0x6d, 0x61, 0x78, 0x41, 0x6e, 0x6f, 0x6e, 0x52, 0x73,
-     0x73, 0x41, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x42, 0x79, 0x74, 0x65,
-     0x73, 0x0a, 0x86, 0x0f, 0x0a, 0x31, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x22, 0xe3, 0x0d, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21,
-     0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61,
-     0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a,
-     0x0e, 0x72, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72,
-     0x74, 0x43, 0x70, 0x75, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x28,
-     0x0a, 0x10, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x0e, 0x64, 0x72, 0x61, 0x77, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x64, 0x72, 0x61, 0x77,
-     0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x72, 0x61, 0x77, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x4d, 0x61, 0x78, 0x12, 0x24, 0x0a, 0x0e, 0x64, 0x72,
-     0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x72, 0x61, 0x77,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0e,
-     0x64, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x61,
-     0x76, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x64, 0x72,
-     0x61, 0x77, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x41, 0x76, 0x67, 0x12, 0x1f,
-     0x0a, 0x0b, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
-     0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66,
-     0x6c, 0x75, 0x73, 0x68, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x08, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x4d, 0x61, 0x78,
-     0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x4d, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6c, 0x75,
-     0x73, 0x68, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01,
-     0x52, 0x08, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x41, 0x76, 0x67, 0x12, 0x2c,
-     0x0a, 0x12, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72,
-     0x65, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x10, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54,
-     0x72, 0x65, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x6d, 0x61, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x72, 0x65, 0x65, 0x4d,
-     0x61, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72,
-     0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x0d,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72,
-     0x65, 0x54, 0x72, 0x65, 0x65, 0x4d, 0x69, 0x6e, 0x12, 0x28, 0x0a, 0x10,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65,
-     0x5f, 0x61, 0x76, 0x67, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e,
-     0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x54, 0x72, 0x65, 0x65, 0x41,
-     0x76, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f,
-     0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x67, 0x70,
-     0x75, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f,
-     0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d,
-     0x61, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x70,
-     0x75, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x4d,
-     0x61, 0x78, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f,
-     0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x70, 0x75, 0x43,
-     0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e,
-     0x12, 0x2c, 0x0a, 0x12, 0x67, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x6d, 0x70,
-     0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x12,
-     0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x67, 0x70, 0x75, 0x43, 0x6f, 0x6d,
-     0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x76, 0x67, 0x12, 0x26,
-     0x0a, 0x0f, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0d, 0x52,
-     0x0d, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x14, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d,
-     0x61, 0x78, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28,
-     0x03, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d,
-     0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63,
-     0x6f, 0x72, 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x16, 0x20, 0x01, 0x28,
-     0x01, 0x52, 0x0b, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x41,
-     0x76, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72,
-     0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x68,
-     0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x43,
-     0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x68, 0x61, 0x64,
-     0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x74,
-     0x69, 0x6d, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x73,
-     0x68, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
-     0x54, 0x69, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x68, 0x61, 0x64,
-     0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x61,
-     0x76, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x73, 0x68,
-     0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x41,
-     0x76, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f,
-     0x68, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1a, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x48, 0x69,
-     0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x61,
-     0x63, 0x68, 0x65, 0x5f, 0x68, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-     0x18, 0x1b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x61, 0x63, 0x68,
-     0x65, 0x48, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x68, 0x69, 0x74, 0x5f, 0x61, 0x76,
-     0x67, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x63, 0x61, 0x63,
-     0x68, 0x65, 0x48, 0x69, 0x74, 0x41, 0x76, 0x67, 0x12, 0x28, 0x0a, 0x10,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e,
-     0x63, 0x61, 0x63, 0x68, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f,
-     0x6d, 0x69, 0x73, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x4d, 0x69,
-     0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x61,
-     0x63, 0x68, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x5f, 0x61, 0x76, 0x67,
-     0x18, 0x1f, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x63, 0x61, 0x63, 0x68,
-     0x65, 0x4d, 0x69, 0x73, 0x73, 0x41, 0x76, 0x67, 0x12, 0x2f, 0x0a, 0x14,
-     0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63, 0x70, 0x75,
-     0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x20, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
-     0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x2f, 0x0a,
-     0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63, 0x70,
-     0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x21, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63,
-     0x73, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x2f,
-     0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x63,
-     0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x22,
-     0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
-     0x63, 0x73, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x41, 0x76, 0x67, 0x12,
-     0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f,
-     0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18,
-     0x23, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70, 0x68,
-     0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78,
-     0x12, 0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
-     0x5f, 0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69, 0x6e,
-     0x18, 0x24, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x67, 0x72, 0x61, 0x70,
-     0x68, 0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x4d, 0x69,
-     0x6e, 0x12, 0x2f, 0x0a, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63,
-     0x73, 0x5f, 0x67, 0x70, 0x75, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76,
-     0x67, 0x18, 0x25, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x67, 0x72, 0x61,
-     0x70, 0x68, 0x69, 0x63, 0x73, 0x47, 0x70, 0x75, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72,
-     0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x26, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65,
-     0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x65,
-     0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x27, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x65, 0x78,
-     0x74, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x26,
-     0x0a, 0x0f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65,
-     0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x28, 0x20, 0x01, 0x28, 0x01, 0x52,
-     0x0d, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65,
-     0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x29, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x09, 0x61, 0x6c, 0x6c, 0x4d, 0x65, 0x6d, 0x4d, 0x61, 0x78, 0x12, 0x1e,
-     0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x69,
-     0x6e, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x6c, 0x6c,
-     0x4d, 0x65, 0x6d, 0x4d, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6c,
-     0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x2b, 0x20,
-     0x01, 0x28, 0x01, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x4d, 0x65, 0x6d, 0x41,
-     0x76, 0x67, 0x22, 0x5a, 0x0a, 0x11, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x48, 0x77, 0x75, 0x69, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x45, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x69,
-     0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52,
-     0x65, 0x6e, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x0a, 0xe0,
-     0x02, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x69,
-     0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x9a, 0x02, 0x0a,
-     0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x40, 0x0a, 0x06, 0x62, 0x75, 0x66,
-     0x66, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49,
-     0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x75, 0x66,
-     0x66, 0x65, 0x72, 0x52, 0x06, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a,
-     0xc3, 0x01, 0x0a, 0x06, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x12,
-     0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76,
-     0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, 0x67, 0x53,
-     0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e,
-     0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74,
-     0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x6d, 0x69,
-     0x6e, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24,
-     0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62,
-     0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c,
-     0x6d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73,
-     0x12, 0x33, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c,
-     0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74,
-     0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x13, 0x74, 0x6f,
-     0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a, 0x65,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x0a, 0xff, 0x04, 0x0a, 0x39, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68,
-     0x65, 0x61, 0x70, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61,
-     0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf8, 0x03, 0x0a,
-     0x11, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x57, 0x0a, 0x0e, 0x69, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73,
-     0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61,
-     0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x1a,
-     0x75, 0x0a, 0x09, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x6f, 0x62, 0x6a, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x0d, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x65,
-     0x0a, 0x06, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02,
-     0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73,
-     0x12, 0x4b, 0x0a, 0x0a, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48,
-     0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x54, 0x79, 0x70,
-     0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65,
-     0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xab, 0x01, 0x0a, 0x0d, 0x49, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
-     0x12, 0x0a, 0x04, 0x75, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0d, 0x52, 0x04, 0x75, 0x70, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74,
-     0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65,
-     0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69,
-     0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x53, 0x61, 0x6d, 0x70,
-     0x6c, 0x65, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x0a,
-     0x9e, 0x06, 0x0a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x74,
-     0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9b,
-     0x05, 0x0a, 0x0d, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x12, 0x53, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74,
-     0x61, 0x6e, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a,
-     0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73,
-     0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61,
-     0x74, 0x73, 0x52, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
-     0x53, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x62, 0x0a, 0x09, 0x48, 0x65, 0x61,
-     0x70, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f,
-     0x6f, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
-     0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a, 0x5f,
-     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0xa6, 0x02,
-     0x0a, 0x06, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02,
-     0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73,
-     0x12, 0x1b, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x68, 0x65, 0x61,
-     0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x62, 0x6a,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x08, 0x6f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x48, 0x65, 0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2e,
-     0x0a, 0x13, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-     0x6f, 0x62, 0x6a, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62,
-     0x6c, 0x65, 0x4f, 0x62, 0x6a, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32,
-     0x0a, 0x16, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x61,
-     0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x61, 0x6e, 0x6f, 0x6e,
-     0x52, 0x73, 0x73, 0x41, 0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x53, 0x69,
-     0x7a, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18,
-     0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74,
-     0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x52, 0x6f, 0x6f, 0x74, 0x73, 0x52,
-     0x05, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x1a, 0xa7, 0x01, 0x0a, 0x0d, 0x49,
-     0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73,
-     0x12, 0x12, 0x0a, 0x04, 0x75, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x0d, 0x52, 0x04, 0x75, 0x70, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x07,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c,
-     0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52,
-     0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x0a, 0xbb, 0x02, 0x0a,
-     0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b,
-     0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf5, 0x01, 0x0a, 0x10, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-     0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x4e, 0x0a, 0x0c, 0x62, 0x79, 0x5f, 0x6f, 0x6f, 0x6d, 0x5f, 0x73,
-     0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4c, 0x6d, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x42, 0x79,
-     0x4f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x52, 0x0a, 0x62, 0x79,
-     0x4f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10,
-     0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6d, 0x5f, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
-     0x6f, 0x6f, 0x6d, 0x56, 0x69, 0x63, 0x74, 0x69, 0x6d, 0x43, 0x6f, 0x75,
-     0x6e, 0x74, 0x1a, 0x46, 0x0a, 0x0a, 0x42, 0x79, 0x4f, 0x6f, 0x6d, 0x53,
-     0x63, 0x6f, 0x72, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f, 0x6d, 0x5f,
-     0x73, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72,
-     0x65, 0x41, 0x64, 0x6a, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e,
-     0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x0a, 0xcc, 0x05, 0x0a, 0x37, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f,
-     0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x22, 0xc7, 0x04, 0x0a, 0x16, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x3f, 0x0a, 0x04, 0x6c, 0x6d,
-     0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d,
-     0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4c, 0x6d, 0x6b, 0x52, 0x04, 0x6c, 0x6d, 0x6b, 0x73, 0x1a,
-     0x97, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12,
-     0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x07, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f,
-     0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63,
-     0x6f, 0x72, 0x65, 0x41, 0x64, 0x6a, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
-     0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69,
-     0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72,
-     0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0c, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x42,
-     0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x6e, 0x6f, 0x6e,
-     0x5f, 0x72, 0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73,
-     0x73, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x68,
-     0x6d, 0x65, 0x6d, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65,
-     0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x68, 0x6d,
-     0x65, 0x6d, 0x52, 0x73, 0x73, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1d,
-     0x0a, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
-     0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x77, 0x61, 0x70,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0xd1, 0x01, 0x0a, 0x03, 0x4c, 0x6d,
-     0x6b, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x6f,
-     0x72, 0x65, 0x5f, 0x61, 0x64, 0x6a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
-     0x52, 0x0b, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x41, 0x64,
-     0x6a, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61,
-     0x70, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0d, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x70, 0x73,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x79, 0x73,
-     0x74, 0x65, 0x6d, 0x5f, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x70,
-     0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x11, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6f, 0x6e, 0x48, 0x65,
-     0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4d, 0x0a, 0x09, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65,
-     0x73, 0x0a, 0x8f, 0x08, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xc9, 0x07, 0x0a, 0x13, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x5c, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xfd, 0x01,
-     0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e,
-     0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c,
-     0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x65, 0x0a, 0x12, 0x70, 0x72,
-     0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b,
-     0x64, 0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x42, 0x72, 0x65,
-     0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f,
-     0x72, 0x69, 0x74, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77,
-     0x6e, 0x1a, 0x87, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69,
-     0x74, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12,
-     0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72,
-     0x69, 0x74, 0x79, 0x12, 0x56, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x08, 0x63,
-     0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x88, 0x03, 0x0a, 0x15,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f, 0x72,
-     0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x47, 0x0a,
-     0x08, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
-     0x72, 0x52, 0x07, 0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73, 0x73, 0x12, 0x47,
-     0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x12,
-     0x40, 0x0a, 0x04, 0x73, 0x77, 0x61, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52,
-     0x04, 0x73, 0x77, 0x61, 0x70, 0x12, 0x50, 0x0a, 0x0d, 0x61, 0x6e, 0x6f,
-     0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x65, 0x72, 0x52, 0x0b, 0x61, 0x6e, 0x6f, 0x6e, 0x41, 0x6e, 0x64, 0x53,
-     0x77, 0x61, 0x70, 0x12, 0x49, 0x0a, 0x09, 0x6a, 0x61, 0x76, 0x61, 0x5f,
-     0x68, 0x65, 0x61, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x08, 0x6a, 0x61,
-     0x76, 0x61, 0x48, 0x65, 0x61, 0x70, 0x1a, 0x3f, 0x0a, 0x07, 0x43, 0x6f,
-     0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12,
-     0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01,
-     0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x76, 0x67,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x76, 0x67, 0x0a,
-     0xa0, 0x06, 0x0a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e, 0x61, 0x67, 0x67, 0x5f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x22, 0xd4, 0x05, 0x0a, 0x1f, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61,
-     0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x65, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55,
-     0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x0d, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x97,
-     0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x56, 0x61,
-     0x6c, 0x75, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-     0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e,
-     0x61, 0x6d, 0x65, 0x12, 0x63, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x5f, 0x76,
-     0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x44, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67,
-     0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d,
-     0x6f, 0x72, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x09, 0x6d,
-     0x65, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3, 0x02, 0x0a,
-     0x13, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x08,
-     0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e,
-     0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07,
-     0x61, 0x6e, 0x6f, 0x6e, 0x52, 0x73, 0x73, 0x12, 0x51, 0x0a, 0x08, 0x66,
-     0x69, 0x6c, 0x65, 0x5f, 0x72, 0x73, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61,
-     0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x66,
-     0x69, 0x6c, 0x65, 0x52, 0x73, 0x73, 0x12, 0x4a, 0x0a, 0x04, 0x73, 0x77,
-     0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65,
-     0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67,
-     0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x56,
-     0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x73, 0x77, 0x61, 0x70, 0x12, 0x5a,
-     0x0a, 0x0d, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73,
-     0x77, 0x61, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d,
-     0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65,
-     0x67, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x6f, 0x6e, 0x41,
-     0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x1a, 0x4a, 0x0a, 0x05, 0x56, 0x61,
-     0x6c, 0x75, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x02, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f,
-     0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x05, 0x52, 0x08, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x6f, 0x72, 0x65,
-     0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x0a, 0x88,
-     0x02, 0x0a, 0x32, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70,
-     0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xc0,
-     0x01, 0x0a, 0x12, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x47, 0x0a,
-     0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x4c, 0x69, 0x73, 0x74, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x61,
-     0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a,
-     0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63,
-     0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
-     0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75,
-     0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
-     0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64,
-     0x65, 0x0a, 0xf0, 0x02, 0x0a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x70, 0x6f, 0x77, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-     0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x11, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69,
-     0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0b, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f,
-     0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x2e,
-     0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0a,
-     0x70, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x4e,
-     0x0a, 0x0a, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-     0x70, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b,
-     0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x12,
-     0x1d, 0x0a, 0x0a, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x5f, 0x75, 0x77,
-     0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x65,
-     0x72, 0x67, 0x79, 0x55, 0x77, 0x73, 0x1a, 0x70, 0x0a, 0x0a, 0x50, 0x6f,
-     0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-     0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x0b, 0x65, 0x6e, 0x65, 0x72,
-     0x67, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28,
-     0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c,
-     0x73, 0x2e, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x52, 0x0a, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61,
-     0x0a, 0xa1, 0x13, 0x0a, 0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f,
-     0x12, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
-     0x47, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01,
-     0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72,
-     0x74, 0x75, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
-     0x1a, 0xe0, 0x01, 0x0a, 0x12, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61,
-     0x74, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12,
-     0x24, 0x0a, 0x0e, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64,
-     0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
-     0x0c, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x75, 0x72, 0x4e,
-     0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c,
-     0x65, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0d, 0x72, 0x75, 0x6e, 0x6e, 0x61, 0x62, 0x6c, 0x65,
-     0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x75, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65,
-     0x5f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x75, 0x72, 0x5f, 0x6e,
-     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x75, 0x6e, 0x69,
-     0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c, 0x65,
-     0x53, 0x6c, 0x65, 0x65, 0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x3b,
-     0x0a, 0x1a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69,
-     0x62, 0x6c, 0x65, 0x5f, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x75,
-     0x72, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17,
-     0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x69, 0x62, 0x6c,
-     0x65, 0x53, 0x6c, 0x65, 0x65, 0x70, 0x44, 0x75, 0x72, 0x4e, 0x73, 0x1a,
-     0x35, 0x0a, 0x05, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x12, 0x15, 0x0a, 0x06,
-     0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-     0x52, 0x05, 0x64, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x64,
-     0x75, 0x72, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
-     0x05, 0x64, 0x75, 0x72, 0x4d, 0x73, 0x1a, 0x80, 0x0b, 0x0a, 0x0c, 0x54,
-     0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12,
-     0x15, 0x0a, 0x06, 0x64, 0x75, 0x72, 0x5f, 0x6e, 0x73, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x75, 0x72, 0x4e, 0x73, 0x12, 0x15,
-     0x0a, 0x06, 0x64, 0x75, 0x72, 0x5f, 0x6d, 0x73, 0x18, 0x11, 0x20, 0x01,
-     0x28, 0x01, 0x52, 0x05, 0x64, 0x75, 0x72, 0x4d, 0x73, 0x12, 0x72, 0x0a,
-     0x19, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x5f, 0x62, 0x79, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x61,
-     0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x72, 0x65,
-     0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x15, 0x6d, 0x61, 0x69, 0x6e,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x42, 0x79, 0x54, 0x61, 0x73, 0x6b,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x41, 0x0a, 0x1d, 0x6f, 0x74, 0x68,
-     0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73,
-     0x5f, 0x73, 0x70, 0x61, 0x77, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75,
-     0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1a, 0x6f, 0x74,
-     0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73,
-     0x53, 0x70, 0x61, 0x77, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-     0x12, 0x5f, 0x0a, 0x15, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74,
-     0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
-     0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61,
-     0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53,
-     0x6c, 0x69, 0x63, 0x65, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x41, 0x63,
-     0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
-     0x72, 0x12, 0x66, 0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63,
-     0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16, 0x74,
-     0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x4d, 0x61, 0x69, 0x6e, 0x12, 0x5f, 0x0a,
-     0x15, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61,
-     0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63,
-     0x65, 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x41,
-     0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5b,
-     0x0a, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76,
-     0x69, 0x74, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20,
-     0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65,
-     0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69,
-     0x74, 0x79, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x5d, 0x0a, 0x14, 0x74,
-     0x69, 0x6d, 0x65, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
-     0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x12,
-     0x74, 0x69, 0x6d, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
-     0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x5a, 0x0a, 0x12, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x63, 0x68, 0x6f, 0x72, 0x65, 0x6f, 0x67, 0x72, 0x61,
-     0x70, 0x68, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x11, 0x74, 0x69, 0x6d,
-     0x65, 0x43, 0x68, 0x6f, 0x72, 0x65, 0x6f, 0x67, 0x72, 0x61, 0x70, 0x68,
-     0x65, 0x72, 0x12, 0x66, 0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x62,
-     0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16,
-     0x74, 0x69, 0x6d, 0x65, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x74,
-     0x61, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x66,
-     0x0a, 0x19, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x69, 0x6e,
-     0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53,
-     0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x16, 0x74, 0x69, 0x6d, 0x65,
-     0x44, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x4d, 0x0a, 0x0c, 0x74, 0x6f,
-     0x5f, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x18, 0x12,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63,
-     0x65, 0x52, 0x0a, 0x74, 0x6f, 0x50, 0x6f, 0x73, 0x74, 0x46, 0x6f, 0x72,
-     0x6b, 0x12, 0x62, 0x0a, 0x17, 0x74, 0x6f, 0x5f, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f,
-     0x6d, 0x61, 0x69, 0x6e, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x14, 0x74, 0x6f, 0x41,
-     0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x4d, 0x61, 0x69, 0x6e, 0x12, 0x5b, 0x0a, 0x13, 0x74, 0x6f, 0x5f,
-     0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
-     0x74, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x11, 0x74, 0x6f, 0x42,
-     0x69, 0x6e, 0x64, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
-     0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x70,
-     0x6f, 0x73, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x18, 0x10, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52,
-     0x0c, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x6f, 0x73, 0x74, 0x46, 0x6f, 0x72,
-     0x6b, 0x4a, 0x04, 0x08, 0x0c, 0x10, 0x0d, 0x4a, 0x04, 0x08, 0x0d, 0x10,
-     0x0e, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10,
-     0x10, 0x1a, 0x5c, 0x0a, 0x0a, 0x48, 0x73, 0x63, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x12, 0x4e, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f,
-     0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x52, 0x0b,
-     0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x1a,
-     0xc2, 0x03, 0x0a, 0x07, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
-     0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x69,
-     0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61,
-     0x72, 0x74, 0x75, 0x70, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
-     0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x7a, 0x79, 0x67, 0x6f,
-     0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x7a, 0x79,
-     0x67, 0x6f, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69,
-     0x74, 0x79, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
-     0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1b, 0x61, 0x63, 0x74, 0x69,
-     0x76, 0x69, 0x74, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
-     0x58, 0x0a, 0x0e, 0x74, 0x6f, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x54, 0x6f, 0x46, 0x69, 0x72, 0x73, 0x74, 0x46, 0x72,
-     0x61, 0x6d, 0x65, 0x52, 0x0c, 0x74, 0x6f, 0x46, 0x69, 0x72, 0x73, 0x74,
-     0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64,
-     0x61, 0x74, 0x61, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
-     0x12, 0x42, 0x0a, 0x03, 0x68, 0x73, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x2e, 0x48, 0x73, 0x63, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x52, 0x03, 0x68, 0x73, 0x63, 0x0a, 0xe4, 0x01, 0x0a,
-     0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x75, 0x72,
-     0x66, 0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x9a,
-     0x01, 0x0a, 0x1b, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x75,
-     0x72, 0x66, 0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69,
-     0x73, 0x73, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x69, 0x73, 0x73, 0x65,
-     0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d,
-     0x69, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x77, 0x63, 0x5f, 0x66, 0x72,
-     0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f,
-     0x6d, 0x69, 0x73, 0x73, 0x65, 0x64, 0x48, 0x77, 0x63, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x73, 0x65,
-     0x64, 0x5f, 0x67, 0x70, 0x75, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x69, 0x73, 0x73,
-     0x65, 0x64, 0x47, 0x70, 0x75, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x0a,
-     0xbb, 0x02, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0xf5, 0x01,
-     0x0a, 0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x61, 0x73,
-     0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x07, 0x70, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x50,
-     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x1a, 0x9b, 0x01, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x21, 0x0a,
-     0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d,
-     0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
-     0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-     0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64,
-     0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12,
-     0x28, 0x0a, 0x10, 0x75, 0x69, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61,
-     0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28,
-     0x09, 0x52, 0x0e, 0x75, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,
-     0x65, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0xe9, 0x06, 0x0a, 0x41, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61,
-     0x74, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x05, 0x0a, 0x1e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69,
-     0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x12, 0x55, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e,
-     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x09, 0x70, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x1a, 0x95, 0x01, 0x0a, 0x11, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65,
-     0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x72, 0x65,
-     0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-     0x08, 0x63, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a,
-     0x0a, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18,
-     0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x74, 0x69,
-     0x6d, 0x65, 0x4d, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x63, 0x79, 0x63,
-     0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d,
-     0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x6f,
-     0x77, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-     0x6d, 0x61, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x70,
-     0x6f, 0x77, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4d,
-     0x61, 0x68, 0x1a, 0xb1, 0x01, 0x0a, 0x06, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-     0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a,
-     0x0b, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
-     0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x12, 0x72, 0x0a, 0x14, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x62, 0x79, 0x5f, 0x63, 0x6f, 0x72,
-     0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x41, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
-     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79,
-     0x70, 0x65, 0x1a, 0x94, 0x02, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x12, 0x43, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x72,
-     0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-     0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-     0x72, 0x0a, 0x14, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x62,
-     0x79, 0x5f, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
-     0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61,
-     0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x42, 0x79, 0x43, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65,
-     0x52, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x79, 0x43,
-     0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x50, 0x0a, 0x07, 0x74,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
-     0x32, 0x36, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65,
-     0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x2e, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x52, 0x07, 0x74, 0x68,
-     0x72, 0x65, 0x61, 0x64, 0x73, 0x0a, 0x98, 0x04, 0x0a, 0x3b, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e, 0x6d, 0x61, 0x70, 0x70,
-     0x65, 0x64, 0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x79, 0x6d, 0x62,
-     0x6f, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8f,
-     0x03, 0x0a, 0x13, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a,
-     0x61, 0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x5c,
-     0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x79,
-     0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-     0x33, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70,
-     0x65, 0x64, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x79, 0x6d,
-     0x62, 0x6f, 0x6c, 0x73, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x1a, 0x4e, 0x0a, 0x05,
-     0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65,
-     0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65,
-     0x12, 0x26, 0x0a, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79,
-     0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-     0x09, 0x52, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x1a, 0xc9, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f,
-     0x63, 0x65, 0x73, 0x73, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12,
-     0x52, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-     0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, 0x65,
-     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c,
-     0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61,
-     0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x2e, 0x46, 0x69,
-     0x65, 0x6c, 0x64, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4a, 0x04,
-     0x08, 0x03, 0x10, 0x04, 0x0a, 0xfc, 0x01, 0x0a, 0x39, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f,
-     0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
-     0xad, 0x01, 0x0a, 0x12, 0x55, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x41,
-     0x0a, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
-     0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x73,
-     0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x05, 0x46, 0x72, 0x61,
-     0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75,
-     0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-     0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75,
-     0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64,
-     0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
-     0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x0a, 0xf0, 0x19, 0x0a, 0x25,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x1a, 0x31, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x62, 0x61, 0x74, 0x74, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x63, 0x70, 0x75, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-     0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x64, 0x69, 0x73,
-     0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3c, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f,
-     0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x31, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-     0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x69, 0x6f, 0x6e,
-     0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a,
-     0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x68, 0x69, 0x73,
-     0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6a, 0x61,
-     0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74,
-     0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x37, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6c, 0x6d, 0x6b, 0x5f, 0x72,
-     0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x36, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e,
-     0x61, 0x67, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x32, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69,
-     0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x35, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x70, 0x6f, 0x77, 0x72, 0x61, 0x69,
-     0x6c, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x34, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
-     0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x75, 0x72, 0x66, 0x61,
-     0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x1a, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x2f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x41, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2f, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x2f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69,
-     0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75, 0x6e,
-     0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f,
-     0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x1a, 0x39, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-     0x63, 0x73, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x75,
-     0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x22, 0xe6, 0x02, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65,
-     0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74,
-     0x72, 0x61, 0x63, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-     0x4e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f,
-     0x75, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x55, 0x75, 0x69, 0x64, 0x12, 0x3a, 0x0a,
-     0x19, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x62, 0x75, 0x69,
-     0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69,
-     0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69,
-     0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x49, 0x0a,
-     0x21, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64, 0x5f, 0x74, 0x72, 0x69, 0x67,
-     0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63,
-     0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x1e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64,
-     0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x75,
-     0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
-     0x12, 0x28, 0x0a, 0x10, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x69,
-     0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x53, 0x69, 0x7a,
-     0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72,
-     0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x18,
-     0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65,
-     0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x75,
-     0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
-     0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
-     0x52, 0x11, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x53, 0x65, 0x73, 0x73,
-     0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10,
-     0x02, 0x22, 0x82, 0x0e, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, 0x0c, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x62, 0x61, 0x74, 0x74, 0x18, 0x05,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x65, 0x72,
-     0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0b, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x42, 0x61, 0x74, 0x74, 0x12, 0x42, 0x0a, 0x0b,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x63, 0x70, 0x75, 0x18,
-     0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66,
-     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
-     0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x43, 0x70, 0x75, 0x12, 0x45, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x4d, 0x65, 0x6d, 0x12, 0x5c, 0x0a, 0x11, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x5f, 0x75, 0x6e, 0x61, 0x67, 0x67,
-     0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x65, 0x72,
-     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-     0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x6f,
-     0x72, 0x79, 0x55, 0x6e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74,
-     0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0f, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4d, 0x65, 0x6d, 0x55, 0x6e, 0x61, 0x67,
-     0x67, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x69, 0x73,
-     0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63,
-     0x6b, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x12, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
-     0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x5f, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x52, 0x0a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x49, 0x6f, 0x6e,
-     0x12, 0x42, 0x0a, 0x0b, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f,
-     0x6c, 0x6d, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
-     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
-     0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c,
-     0x6d, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0a, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x12, 0x4d, 0x0a, 0x10,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70, 0x6f, 0x77, 0x72,
-     0x61, 0x69, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x50, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0f,
-     0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x50, 0x6f, 0x77, 0x72, 0x61,
-     0x69, 0x6c, 0x73, 0x12, 0x4e, 0x0a, 0x0f, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02,
-     0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
-     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41,
-     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
-     0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
-     0x5b, 0x0a, 0x16, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x66,
-     0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
-     0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
-     0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x14,
-     0x68, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43,
-     0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0e,
-     0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-     0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61,
-     0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x4d,
-     0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x54, 0x0a, 0x13, 0x75,
-     0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f,
-     0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b,
-     0x32, 0x23, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
-     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x55, 0x6e, 0x73, 0x79, 0x6d,
-     0x62, 0x6f, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65,
-     0x73, 0x52, 0x12, 0x75, 0x6e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x69,
-     0x7a, 0x65, 0x64, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x46, 0x0a,
-     0x0f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73,
-     0x74, 0x61, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
-     0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72,
-     0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61,
-     0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x6a, 0x61, 0x76, 0x61,
-     0x48, 0x65, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x52, 0x0a,
-     0x13, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x68,
-     0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, 0x15, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4a, 0x61, 0x76,
-     0x61, 0x48, 0x65, 0x61, 0x70, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72,
-     0x61, 0x6d, 0x52, 0x11, 0x6a, 0x61, 0x76, 0x61, 0x48, 0x65, 0x61, 0x70,
-     0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x55, 0x0a,
-     0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6c, 0x6d, 0x6b,
-     0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x4c, 0x6d, 0x6b, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
-     0x12, 0x58, 0x0a, 0x15, 0x75, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64,
-     0x5f, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c,
-     0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61,
-     0x76, 0x61, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x52, 0x13, 0x75,
-     0x6e, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x4a, 0x61, 0x76, 0x61, 0x53,
-     0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x12, 0x52, 0x0a, 0x13, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x68, 0x77, 0x75, 0x69, 0x5f, 0x6d,
-     0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x22, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
-     0x64, 0x48, 0x77, 0x75, 0x69, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52,
-     0x11, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x48, 0x77, 0x75, 0x69,
-     0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x69,
-     0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x44, 0x69, 0x73,
-     0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52,
-     0x0e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x73, 0x12, 0x4f, 0x0a, 0x12, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-     0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x65,
-     0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x54, 0x61, 0x73,
-     0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x52, 0x10, 0x61, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x73,
-     0x12, 0x6f, 0x0a, 0x1c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f,
-     0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f,
-     0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x18, 0x20, 0x01,
-     0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
-     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69,
-     0x6d, 0x65, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
-     0x72, 0x69, 0x63, 0x52, 0x18, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-     0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x49, 0x6e,
-     0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x63, 0x0a, 0x16, 0x61, 0x6e, 0x64,
-     0x72, 0x6f, 0x69, 0x64, 0x5f, 0x73, 0x75, 0x72, 0x66, 0x61, 0x63, 0x65,
-     0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28,
-     0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
-     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x41, 0x6e, 0x64, 0x72,
-     0x6f, 0x69, 0x64, 0x53, 0x75, 0x72, 0x66, 0x61, 0x63, 0x65, 0x66, 0x6c,
-     0x69, 0x6e, 0x67, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52,
-     0x15, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x75, 0x72, 0x66,
-     0x61, 0x63, 0x65, 0x66, 0x6c, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x2a, 0x06,
-     0x08, 0xc2, 0x03, 0x10, 0xf4, 0x03, 0x2a, 0x06, 0x08, 0xf4, 0x03, 0x10,
-     0xe9, 0x07, 0x2a, 0x06, 0x08, 0xe9, 0x07, 0x10, 0xd1, 0x0f, 0x4a, 0x04,
-     0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x4a, 0x04,
-     0x08, 0x0d, 0x10, 0x0e, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f}};
-
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_METRICS_METRICS_DESCRIPTOR_H_
diff --git a/src/trace_processor/metrics/metrics_unittest.cc b/src/trace_processor/metrics/metrics_unittest.cc
index abbb57a..661df33 100644
--- a/src/trace_processor/metrics/metrics_unittest.cc
+++ b/src/trace_processor/metrics/metrics_unittest.cc
@@ -74,7 +74,8 @@
   // message TestProto {
   //   optional int64 int_value = 1;
   // }
-  ProtoDescriptor descriptor(".perfetto.protos", ".perfetto.protos.TestProto",
+  ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
+                             ".perfetto.protos.TestProto",
                              ProtoDescriptor::Type::kMessage, base::nullopt);
   descriptor.AddField(FieldDescriptor(
       "int_value", 1, FieldDescriptorProto::TYPE_INT64, "", false));
@@ -95,7 +96,8 @@
   // message TestProto {
   //   optional double double_value = 1;
   // }
-  ProtoDescriptor descriptor(".perfetto.protos", ".perfetto.protos.TestProto",
+  ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
+                             ".perfetto.protos.TestProto",
                              ProtoDescriptor::Type::kMessage, base::nullopt);
   descriptor.AddField(FieldDescriptor(
       "double_value", 1, FieldDescriptorProto::TYPE_DOUBLE, "", false));
@@ -116,7 +118,8 @@
   // message TestProto {
   //   optional string string_value = 1;
   // }
-  ProtoDescriptor descriptor(".perfetto.protos", ".perfetto.protos.TestProto",
+  ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
+                             ".perfetto.protos.TestProto",
                              ProtoDescriptor::Type::kMessage, base::nullopt);
   descriptor.AddField(FieldDescriptor(
       "string_value", 1, FieldDescriptorProto::TYPE_STRING, "", false));
@@ -140,13 +143,14 @@
   //   }
   //   optional NestedProto nested_value = 1;
   // }
-  ProtoDescriptor nested(".perfetto.protos",
+  ProtoDescriptor nested("file.proto", ".perfetto.protos",
                          ".perfetto.protos.TestProto.NestedProto",
                          ProtoDescriptor::Type::kMessage, base::nullopt);
   nested.AddField(FieldDescriptor("nested_int_value", 1,
                                   FieldDescriptorProto::TYPE_INT64, "", false));
 
-  ProtoDescriptor descriptor(".perfetto.protos", ".perfetto.protos.TestProto",
+  ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
+                             ".perfetto.protos.TestProto",
                              ProtoDescriptor::Type::kMessage, base::nullopt);
   auto field =
       FieldDescriptor("nested_value", 1, FieldDescriptorProto::TYPE_MESSAGE,
@@ -186,7 +190,8 @@
   // message TestProto {
   //   repeated int64 int_value = 1;
   // }
-  ProtoDescriptor descriptor(".perfetto.protos", ".perfetto.protos.TestProto",
+  ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
+                             ".perfetto.protos.TestProto",
                              ProtoDescriptor::Type::kMessage, base::nullopt);
   descriptor.AddField(FieldDescriptor(
       "rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "", true));
diff --git a/src/trace_processor/metrics/webview/webview_power_usage.sql b/src/trace_processor/metrics/webview/webview_power_usage.sql
index 2fc71ec..7ac8efc 100644
--- a/src/trace_processor/metrics/webview/webview_power_usage.sql
+++ b/src/trace_processor/metrics/webview/webview_power_usage.sql
@@ -20,67 +20,259 @@
 -- This file populates a summary table that can be used to produce metrics in different formats.
 
 SELECT RUN_METRIC('android/android_proxy_power.sql');
+SELECT RUN_METRIC('android/cpu_info.sql');
 
-DROP VIEW IF EXISTS webview_slice;
+DROP VIEW IF EXISTS top_level_slice;
 
-CREATE VIEW webview_slice AS
+-- Select topmost slices from the 'toplevel' and 'Java' categories.
+-- Filter out Looper events since they are likely to belong to the host app.
+-- Slices are only used to calculate the contribution of the browser process,
+-- renderer contribution will be calculated as the sum of all renderer processes' usage.
+CREATE VIEW top_level_slice AS
   SELECT *
-  FROM
-    -- TODO(b/156788923): add better conditions.
-    slice WHERE category = 'android_webview';
+  FROM slice WHERE
+  depth = 0 AND
+  ((category like '%toplevel%' OR category = 'Java') AND
+  name NOT LIKE '%looper%');
 
-DROP VIEW IF EXISTS top_level_webview_slice;
+DROP VIEW IF EXISTS webview_browser_slices;
 
--- Since we filter out some slices in webview_slice above, we cannot use the "depth" column
--- to select only the top-level webview slices. Instead, we have to join webview_slice with itself,
--- selecting only those events that do not have any children in webview_slice.
-CREATE VIEW top_level_webview_slice AS
-  SELECT *
-  FROM
-    webview_slice s1 WHERE
-  (SELECT COUNT(1)
-  FROM (
-    SELECT *
-    FROM
-      webview_slice s2
-    WHERE s1.track_id = s2.track_id
-    AND s2.ts < s1.ts
-    AND s2.ts + s2.dur > s1.ts + s1.dur
-    LIMIT 1)
-   ) == 0;
-
-DROP VIEW IF EXISTS webview_threads;
-
--- Match WebView slices to threads and processes.
--- Process information will be used in the future to determine the contribution of all
--- WebView process types (browser, renderer) to a specific app's power usage.
-CREATE VIEW webview_threads AS
+-- Match top-level slices to threads and hosting apps.
+-- This excludes any renderer slices because renderer processes are counted
+-- as a whole separately.
+-- Slices from Chrome browser processes are also excluded.
+CREATE VIEW webview_browser_slices AS
   SELECT
-    top_level_webview_slice.ts,
-    top_level_webview_slice.dur,
+    top_level_slice.ts,
+    top_level_slice.dur,
     thread_track.utid,
-    process.name AS app_name
-FROM top_level_webview_slice
+    process.upid AS upid,
+    extract_arg(process.arg_set_id, 'chrome.host_app_package_name') AS app_name
+  FROM top_level_slice
   INNER JOIN thread_track
-  ON top_level_webview_slice.track_id = thread_track.id
+  ON top_level_slice.track_id = thread_track.id
   INNER JOIN process
   ON thread.upid = process.upid
   INNER JOIN thread
-  ON thread_track.utid = thread.utid;
+  ON thread_track.utid = thread.utid
+  WHERE process.name NOT LIKE '%SandboxedProcessService%'
+  AND process.name NOT LIKE '%chrome%'
+  AND app_name IS NOT NULL;
 
-DROP TABLE IF EXISTS webview_power;
+DROP TABLE IF EXISTS webview_browser_slices_power;
 
--- Assign power usage to WebView slices.
-CREATE VIRTUAL TABLE webview_power
+-- Assign power usage to WebView browser slices.
+CREATE VIRTUAL TABLE webview_browser_slices_power
 USING SPAN_JOIN(power_per_thread PARTITIONED utid,
-               webview_threads PARTITIONED utid);
+                webview_browser_slices PARTITIONED utid);
 
-DROP VIEW IF EXISTS webview_power_summary;
+DROP VIEW IF EXISTS webview_browser_slices_power_summary;
 
--- Calculate the power usage of all WebView slices for each app in milliampere-seconds.
-CREATE VIEW webview_power_summary AS
+-- Calculate the power usage of all WebView browser slices for each app
+-- in milliampere-seconds.
+CREATE VIEW webview_browser_slices_power_summary AS
 SELECT
   app_name,
-  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS webview_power_mas
-  FROM webview_power
-GROUP BY app_name;
+  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+  FROM webview_browser_slices_power
+  GROUP BY app_name;
+
+DROP VIEW IF EXISTS webview_renderer_threads;
+
+-- All threads of all WebView renderer processes.
+CREATE VIEW webview_renderer_threads AS
+SELECT
+  thread.utid AS utid,
+  extract_arg(process.arg_set_id, 'chrome.host_app_package_name') as app_name
+  FROM process
+  INNER JOIN thread
+  ON thread.upid = process.upid
+  WHERE process.name LIKE '%webview%SandboxedProcessService%'
+  AND app_name IS NOT NULL;
+
+DROP VIEW IF EXISTS webview_renderer_power_summary;
+
+-- Calculate the power usage of all WebView renderer processes for each app
+-- in milliampere-seconds.
+CREATE VIEW webview_renderer_power_summary AS
+  SELECT
+    app_name,
+    SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+  FROM power_per_thread
+  INNER JOIN webview_renderer_threads
+  ON power_per_thread.utid = webview_renderer_threads.utid
+  GROUP BY app_name;
+
+DROP VIEW IF EXISTS webview_renderer_power_per_core_type;
+
+-- Calculate the power usage of all WebView renderer processes for each app
+-- in milliampere-seconds grouped by core type.
+CREATE VIEW webview_renderer_power_per_core_type AS
+SELECT
+  app_name,
+  core_type_per_cpu.core_type AS core_type,
+  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+FROM power_per_thread
+INNER JOIN webview_renderer_threads
+  ON power_per_thread.utid = webview_renderer_threads.utid
+INNER JOIN core_type_per_cpu
+  ON power_per_thread.cpu = core_type_per_cpu.cpu
+GROUP BY app_name, core_type_per_cpu.core_type;
+
+DROP VIEW IF EXISTS host_app_threads;
+
+-- All threads of hosting apps (this is a superset of webview_browser_slices).
+-- 1) select all threads that had any WebView browser slices associated with them;
+-- 2) get all threads for processes matching threads from 1).
+-- For example, only some of app's threads wrote any slices, but we are selecting
+-- all threads for this app's process.
+-- Excludes all renderer processes and Chrome browser processes.
+CREATE VIEW host_app_threads AS
+SELECT
+    thread.utid AS utid,
+    thread.name AS name,
+    extract_arg(process.arg_set_id, 'chrome.host_app_package_name') as app_name
+  FROM thread
+  JOIN process ON thread.upid = process.upid
+  WHERE thread.upid IN
+    (SELECT DISTINCT(webview_browser_slices.upid) FROM webview_browser_slices)
+  AND process.name NOT LIKE '%SandboxedProcessService%'
+  AND process.name NOT LIKE '%chrome%'
+  AND app_name IS NOT NULL;
+
+DROP VIEW IF EXISTS host_app_power_summary;
+
+-- Calculate the power usage of all WebView (host app+browser) processes for each app
+-- in milliampere-seconds.
+CREATE VIEW host_app_power_summary AS
+  SELECT
+    app_name,
+    SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+  FROM power_per_thread
+  INNER JOIN host_app_threads
+  ON power_per_thread.utid = host_app_threads.utid
+  GROUP BY app_name;
+
+DROP VIEW IF EXISTS host_app_power_per_core_type;
+
+-- Calculate the power usage of all WebView (host app+browser) processes for each app
+-- in milliampere-seconds grouped by core type.
+CREATE VIEW host_app_power_per_core_type AS
+SELECT
+  app_name,
+  core_type_per_cpu.core_type AS core_type,
+  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+FROM power_per_thread
+INNER JOIN host_app_threads
+  ON power_per_thread.utid = host_app_threads.utid
+INNER JOIN core_type_per_cpu
+  ON power_per_thread.cpu = core_type_per_cpu.cpu
+GROUP BY app_name, core_type_per_cpu.core_type;
+
+DROP VIEW IF EXISTS webview_only_threads;
+
+-- A subset of the host app threads that are WebView-specific.
+CREATE VIEW webview_only_threads AS
+SELECT *
+FROM host_app_threads
+  WHERE name LIKE 'Chrome%' OR name LIKE 'CookieMonster%'
+  OR name LIKE 'CompositorTileWorker%' OR name LIKE 'ThreadPool%ground%'
+  OR NAME LIKE 'ThreadPoolService%' OR  name like 'VizCompositorThread%'
+  OR name IN ('AudioThread', 'DedicatedWorker thread', 'GpuMemoryThread',
+  'JavaBridge', 'LevelDBEnv.IDB', 'MemoryInfra', 'NetworkService', 'VizWebView');
+
+DROP VIEW IF EXISTS webview_only_power_summary;
+
+-- Calculate the power usage of all WebView-specific host app threads
+-- (browser + in-process renderers) for each app in milliampere-seconds.
+CREATE VIEW webview_only_power_summary AS
+  SELECT
+    app_name,
+    SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+  FROM power_per_thread
+  INNER JOIN webview_only_threads
+  ON power_per_thread.utid = webview_only_threads.utid
+  GROUP BY app_name;
+
+DROP VIEW IF EXISTS webview_only_power_per_core_type;
+
+-- Calculate the power usage of all WebView-specific host app threads
+-- for each app in milliampere-seconds grouped by core type.
+CREATE VIEW webview_only_power_per_core_type AS
+  SELECT app_name,
+  core_type_per_cpu.core_type AS core_type,
+  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+FROM power_per_thread
+INNER JOIN webview_only_threads
+  ON power_per_thread.utid = webview_only_threads.utid
+INNER JOIN core_type_per_cpu
+  ON power_per_thread.cpu = core_type_per_cpu.cpu
+GROUP BY app_name, core_type_per_cpu.core_type;
+
+-- Create views for output.
+
+DROP VIEW IF EXISTS total_app_power_output;
+
+CREATE VIEW total_app_power_output AS
+  SELECT
+    host_app_power_summary.app_name as app_name,
+    host_app_power_summary.power_mas AS total_mas,
+    host_app_power_little_cores_mas.power_mas AS little_cores_mas,
+    host_app_power_big_cores_mas.power_mas AS big_cores_mas,
+    host_app_power_bigger_cores_mas.power_mas AS bigger_cores_mas
+  FROM host_app_power_summary LEFT JOIN host_app_power_per_core_type AS host_app_power_little_cores_mas
+  ON host_app_power_summary.app_name = host_app_power_little_cores_mas.app_name
+  AND host_app_power_little_cores_mas.core_type = 'little'
+  LEFT JOIN host_app_power_per_core_type AS host_app_power_big_cores_mas
+  ON host_app_power_summary.app_name = host_app_power_big_cores_mas.app_name
+  AND host_app_power_big_cores_mas.core_type = 'big'
+  LEFT JOIN host_app_power_per_core_type AS host_app_power_bigger_cores_mas
+  ON host_app_power_summary.app_name = host_app_power_bigger_cores_mas.app_name
+  AND host_app_power_bigger_cores_mas.core_type = 'bigger';
+
+DROP VIEW IF EXISTS webview_renderer_power_output;
+
+CREATE VIEW webview_renderer_power_output AS
+  SELECT
+    webview_renderer_power_summary.app_name AS app_name,
+    webview_renderer_power_summary.power_mas AS total_mas,
+    webview_renderer_little_power.power_mas AS little_cores_mas,
+    webview_renderer_big_power.power_mas AS big_cores_mas,
+    webview_renderer_bigger_power.power_mas AS bigger_cores_mas
+  FROM webview_renderer_power_summary LEFT JOIN webview_renderer_power_per_core_type AS webview_renderer_little_power
+  ON webview_renderer_power_summary.app_name = webview_renderer_little_power.app_name
+  AND webview_renderer_little_power.core_type = 'little'
+  LEFT JOIN webview_renderer_power_per_core_type AS webview_renderer_big_power
+  ON webview_renderer_power_summary.app_name = webview_renderer_big_power.app_name
+  AND webview_renderer_big_power.core_type = 'big'
+  LEFT JOIN webview_renderer_power_per_core_type AS webview_renderer_bigger_power
+  ON webview_renderer_power_summary.app_name = webview_renderer_bigger_power.app_name
+  AND webview_renderer_bigger_power.core_type = 'bigger';
+
+DROP VIEW IF EXISTS webview_only_power_output;
+
+CREATE VIEW webview_only_power_output AS
+  SELECT
+    webview_only_power_summary.app_name AS app_name,
+    webview_only_power_summary.power_mas AS total_mas,
+    webview_only_power_little_cores_mas.power_mas AS little_cores_mas,
+    webview_only_power_big_cores_mas.power_mas AS big_cores_mas,
+    webview_only_power_bigger_cores_mas.power_mas AS bigger_cores_mas
+  FROM webview_only_power_summary LEFT JOIN webview_only_power_per_core_type AS webview_only_power_little_cores_mas
+  ON webview_only_power_summary.app_name = webview_only_power_little_cores_mas.app_name
+  AND webview_only_power_little_cores_mas.core_type = 'little'
+  LEFT JOIN webview_only_power_per_core_type AS webview_only_power_big_cores_mas
+  ON webview_only_power_summary.app_name = webview_only_power_big_cores_mas.app_name
+  AND webview_only_power_big_cores_mas.core_type = 'big'
+  LEFT JOIN webview_only_power_per_core_type AS webview_only_power_bigger_cores_mas
+  ON webview_only_power_summary.app_name = webview_only_power_bigger_cores_mas.app_name
+  AND webview_only_power_bigger_cores_mas.core_type = 'bigger';
+
+DROP VIEW IF EXISTS total_device_power;
+
+-- Calculate the power usage of the device in milliampere-seconds.
+CREATE VIEW total_device_power AS
+  SELECT SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+  FROM power_per_thread;
+
+DROP VIEW IF EXISTS webview_power_summary;
diff --git a/src/trace_processor/python/LICENSE b/src/trace_processor/python/LICENSE
new file mode 100644
index 0000000..ab4c4a2
--- /dev/null
+++ b/src/trace_processor/python/LICENSE
@@ -0,0 +1,189 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   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.
+
+   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.
+
+
diff --git a/src/trace_processor/python/README.md b/src/trace_processor/python/README.md
new file mode 100644
index 0000000..3ef3430
--- /dev/null
+++ b/src/trace_processor/python/README.md
@@ -0,0 +1,9 @@
+# Perfetto - System profiling, app tracing and trace analysis
+
+Perfetto is a production-grade open-source stack for performance
+instrumentation and trace analysis. It offers services and libraries and for
+recording system-level and app-level traces, native + java heap profiling, a
+library for analyzing traces using SQL and a web-based UI to visualize and
+explore multi-GB traces.
+
+See https://perfetto.dev/docs or the /docs/ directory for documentation.
diff --git a/src/trace_processor/python/example.py b/src/trace_processor/python/example.py
index 9f6c784..b3925a4 100644
--- a/src/trace_processor/python/example.py
+++ b/src/trace_processor/python/example.py
@@ -15,7 +15,7 @@
 
 import argparse
 
-from trace_processor.api import TraceProcessor
+from perfetto.trace_processor import TraceProcessor
 
 
 def main():
@@ -45,10 +45,21 @@
     tp = TraceProcessor(
         addr=args.address, file_path=args.file, bin_path=args.binary)
 
-  # Call functions on the loaded trace
+  # Iterate through QueryResultIterator
   res_it = tp.query('select * from slice limit 10')
   for row in res_it:
     print(row.name)
+
+  # Convert QueryResultIterator into a pandas dataframe + iterate. This yields
+  # the same results as the function above.
+  try:
+    res_df = tp.query('select * from slice limit 10').as_pandas_dataframe()
+    for index, row in res_df.iterrows():
+      print(row['name'])
+  except Exception:
+    pass
+
+  # Call another function on the loaded trace
   am_metrics = tp.metric(['android_mem'])
   tp.close()
 
diff --git a/src/trace_processor/python/perfetto/__init__.py b/src/trace_processor/python/perfetto/__init__.py
new file mode 100644
index 0000000..c569069
--- /dev/null
+++ b/src/trace_processor/python/perfetto/__init__.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+# 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.
+
+try:
+  __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+  __path__ = __import__('pkgutil').extend_path(__path__, __name__)
\ No newline at end of file
diff --git a/src/trace_processor/python/perfetto/trace_processor/__init__.py b/src/trace_processor/python/perfetto/trace_processor/__init__.py
new file mode 100644
index 0000000..fbbffcd
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/__init__.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+# 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.
+
+from .api import TraceProcessor
+from .http import TraceProcessorHttp
\ No newline at end of file
diff --git a/src/trace_processor/python/perfetto/trace_processor/api.py b/src/trace_processor/python/perfetto/trace_processor/api.py
new file mode 100644
index 0000000..9cabf07
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/api.py
@@ -0,0 +1,245 @@
+# 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.
+
+from urllib.parse import urlparse
+
+from .http import TraceProcessorHttp
+from .loader import get_loader
+from .protos import ProtoFactory
+from .shell import load_shell
+
+
+# Custom exception raised if any trace_processor functions return a
+# response with an error defined
+class TraceProcessorException(Exception):
+
+  def __init__(self, message):
+    super().__init__(message)
+
+
+class TraceProcessor:
+
+  # Values of these constants correspond to the QueryResponse message at
+  # protos/perfetto/trace_processor/trace_processor.proto
+  # Value 0 corresponds to CELL_INVALID, which is represented as None in
+  # this class
+  QUERY_CELL_NULL_FIELD_ID = 1
+  QUERY_CELL_VARINT_FIELD_ID = 2
+  QUERY_CELL_FLOAT64_FIELD_ID = 3
+  QUERY_CELL_STRING_FIELD_ID = 4
+  QUERY_CELL_BLOB_FIELD_ID = 5
+
+  # This is the class returned to the user and contains one row of the
+  # resultant query. Each column name is stored as an attribute of this
+  # class, with the value corresponding to the column name and row in
+  # the query results table.
+  class Row(object):
+
+    def __str__(self):
+      return str(self.__dict__)
+
+    def __repr__(self):
+      return self.__dict__
+
+  class QueryResultIterator:
+
+    def __init__(self, column_names, batches):
+      self.__batches = batches
+      self.__column_names = column_names
+      self.__batch_index = 0
+      self.__next_index = 0
+      # TODO(lalitm): Look into changing string_cells to bytes in the protobuf
+      self.__string_cells = memoryview(bytes(batches[0].string_cells, 'utf-8'))
+      self.__string_index = 0
+
+    def get_cell_list(self, proto_index):
+      if proto_index == TraceProcessor.QUERY_CELL_NULL_FIELD_ID:
+        return None
+      elif proto_index == TraceProcessor.QUERY_CELL_VARINT_FIELD_ID:
+        return self.__batches[self.__batch_index].varint_cells
+      elif proto_index == TraceProcessor.QUERY_CELL_FLOAT64_FIELD_ID:
+        return self.__batches[self.__batch_index].float64_cells
+      elif proto_index == TraceProcessor.QUERY_CELL_BLOB_FIELD_ID:
+        return self.__batches[self.__batch_index].blob_cells
+      else:
+        raise TraceProcessorException('Invalid cell type')
+
+    def cells(self):
+      return self.__batches[self.__batch_index].cells
+
+    # To use the query result as a populated Pandas dataframe, this
+    # function must be called directly after calling query inside
+    # TraceProcesor.
+    def as_pandas_dataframe(self):
+      try:
+        import numpy as np
+        import pandas as pd
+
+        df = pd.DataFrame(columns=self.__column_names)
+
+        # Populate the dataframe with the query results
+        while True:
+          # If all cells are read, then check if last batch before
+          # returning the populated dataframe
+          if self.__next_index >= len(self.__batches[self.__batch_index].cells):
+            if self.__batches[self.__batch_index].is_last_batch:
+              ordered_df = df.reset_index(drop=True)
+              return ordered_df
+            self.__batch_index += 1
+            self.__next_index = 0
+            self.__string_cells = memoryview(
+                bytes(self.__batches[self.__batch_index].string_cells, 'utf-8'))
+            self.__string_index = 0
+
+          row = []
+          for num, column_name in enumerate(self.__column_names):
+            cell_type = self.__batches[self.__batch_index].cells[
+                self.__next_index + num]
+            if cell_type == TraceProcessor.QUERY_CELL_STRING_FIELD_ID:
+              start_index = self.__string_index
+              while self.__string_cells[self.__string_index] != 0:
+                self.__string_index += 1
+              row.append(
+                  str(self.__string_cells[start_index:self.__string_index],
+                      'utf-8'))
+              self.__string_index += 1
+            else:
+              cell_list = self.get_cell_list(cell_type)
+              if cell_list is None:
+                row.append(np.NAN)
+              else:
+                row.append(cell_list.pop(0))
+          df.loc[-1] = row
+          df.index = df.index + 1
+          self.__next_index = self.__next_index + len(self.__column_names)
+
+      except ModuleNotFoundError:
+        raise TraceProcessorException(
+            'The sufficient libraries are not installed')
+
+    def __iter__(self):
+      return self
+
+    def __next__(self):
+      # If all cells are read, then check if last batch before raising
+      # StopIteration
+      if self.__next_index >= len(self.cells()):
+        if self.__batches[self.__batch_index].is_last_batch:
+          raise StopIteration
+        self.__batch_index += 1
+        self.__next_index = 0
+        self.__string_cells = memoryview(
+            bytes(self.__batches[self.__batch_index].string_cells, 'utf-8'))
+        self.__string_index = 0
+
+      row = TraceProcessor.Row()
+      for num, column_name in enumerate(self.__column_names):
+        cell_type = self.__batches[self.__batch_index].cells[self.__next_index +
+                                                             num]
+        if cell_type == TraceProcessor.QUERY_CELL_STRING_FIELD_ID:
+          start_index = self.__string_index
+          while self.__string_cells[self.__string_index] != 0:
+            self.__string_index += 1
+          setattr(
+              row, column_name,
+              str(self.__string_cells[start_index:self.__string_index],
+                  'utf-8'))
+          self.__string_index += 1
+        else:
+          cell_list = self.get_cell_list(cell_type)
+          if cell_list is None:
+            setattr(row, column_name, None)
+          else:
+            setattr(row, column_name, cell_list.pop(0))
+      self.__next_index = self.__next_index + len(self.__column_names)
+      return row
+
+  def __init__(self, addr=None, file_path=None, bin_path=None,
+               unique_port=True):
+    # Load trace_processor_shell or access via given address
+    if addr:
+      p = urlparse(addr)
+      tp = TraceProcessorHttp(p.netloc if p.netloc else p.path)
+    else:
+      url, self.subprocess = load_shell(
+          bin_path=bin_path, unique_port=unique_port)
+      tp = TraceProcessorHttp(url)
+    self.http = tp
+    self.protos = ProtoFactory()
+
+    # Parse trace by its file_path into the loaded instance of trace_processor
+    if file_path:
+      get_loader().parse_file(self.http, file_path)
+
+  def query(self, sql):
+    """Executes passed in SQL query using class defined HTTP API, and returns
+    the response as a QueryResultIterator. Raises TraceProcessorException if
+    the response returns with an error.
+
+    Args:
+      sql: SQL query written as a String
+
+    Returns:
+      A class which can iterate through each row of the results table. This
+      can also be converted to a pandas dataframe by calling the
+      as_pandas_dataframe() function after calling query.
+    """
+    response = self.http.execute_query(sql)
+    if response.error:
+      raise TraceProcessorException(response.error)
+
+    return TraceProcessor.QueryResultIterator(response.column_names,
+                                              response.batch)
+
+  def metric(self, metrics):
+    """Returns the metrics data corresponding to the passed in trace metric.
+    Raises TraceProcessorException if the response returns with an error.
+
+    Args:
+      metrics: A list of valid metrics as defined in TraceMetrics
+
+    Returns:
+      The metrics data as a proto message
+    """
+    response = self.http.compute_metric(metrics)
+    if response.error:
+      raise TraceProcessorException(response.error)
+
+    metrics = self.protos.TraceMetrics()
+    metrics.ParseFromString(response.metrics)
+    return metrics
+
+  def enable_metatrace(self):
+    """Enable metatrace for the currently running trace_processor.
+    """
+    return self.http.enable_metatrace()
+
+  def disable_and_read_metatrace(self):
+    """Disable and return the metatrace formed from the currently running
+    trace_processor. This must be enabled before attempting to disable. This
+    returns the serialized bytes of the metatrace data directly. Raises
+    TraceProcessorException if the response returns with an error.
+    """
+    response = self.http.disable_and_read_metatrace()
+    if response.error:
+      raise TraceProcessorException(response.error)
+
+    return response.metatrace
+
+  # TODO(@aninditaghosh): Investigate context managers for
+  # cleaner usage
+  def close(self):
+    if hasattr(self, 'subprocess'):
+      self.subprocess.kill()
+    self.http.conn.close()
diff --git a/src/trace_processor/python/trace_processor/http.py b/src/trace_processor/python/perfetto/trace_processor/http.py
similarity index 60%
rename from src/trace_processor/python/trace_processor/http.py
rename to src/trace_processor/python/perfetto/trace_processor/http.py
index bff6e10..bf751f9 100644
--- a/src/trace_processor/python/trace_processor/http.py
+++ b/src/trace_processor/python/perfetto/trace_processor/http.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from urllib import request
+import http.client
 
 from .protos import ProtoFactory
 
@@ -22,50 +22,53 @@
 
   def __init__(self, url):
     self.protos = ProtoFactory()
-    self.url = 'http://' + url
+    self.conn = http.client.HTTPConnection(url)
 
   def execute_query(self, query):
     args = self.protos.RawQueryArgs()
     args.sql_query = query
     byte_data = args.SerializeToString()
-    req = request.Request(self.url + '/query', data=byte_data)
-    with request.urlopen(req) as f:
+    self.conn.request('POST', '/query', body=byte_data)
+    with self.conn.getresponse() as f:
       result = self.protos.QueryResult()
       result.ParseFromString(f.read())
-
-    if result.error:
-      raise Exception(result.error)
-    return result
+      return result
 
   def compute_metric(self, metrics):
     args = self.protos.ComputeMetricArgs()
     args.metric_names.extend(metrics)
     byte_data = args.SerializeToString()
-    req = request.Request(self.url + '/compute_metric', data=byte_data)
-    with request.urlopen(req) as f:
+    self.conn.request('POST', '/compute_metric', body=byte_data)
+    with self.conn.getresponse() as f:
       result = self.protos.ComputeMetricResult()
       result.ParseFromString(f.read())
-
-    if result.error:
-      raise Exception(result.error)
-
-    metrics = self.protos.TraceMetrics()
-    metrics.ParseFromString(result.metrics)
-    return metrics
+      return result
 
   def parse(self, chunk):
-    req = request.Request(self.url + '/parse', data=chunk)
-    with request.urlopen(req) as f:
+    self.conn.request('POST', '/parse', body=chunk)
+    with self.conn.getresponse() as f:
       return f.read()
 
   def notify_eof(self):
-    req = request.Request(self.url + '/notify_eof')
-    with request.urlopen(req) as f:
+    self.conn.request('GET', '/notify_eof')
+    with self.conn.getresponse() as f:
       return f.read()
 
   def status(self):
-    req = request.Request(self.url + '/status')
-    with request.urlopen(req) as f:
+    self.conn.request('GET', '/status')
+    with self.conn.getresponse() as f:
       result = self.protos.StatusResult()
       result.ParseFromString(f.read())
       return result
+
+  def enable_metatrace(self):
+    self.conn.request('GET', '/enable_metatrace')
+    with self.conn.getresponse() as f:
+      return f.read()
+
+  def disable_and_read_metatrace(self):
+    self.conn.request('GET', '/disable_and_read_metatrace')
+    with self.conn.getresponse() as f:
+      result = self.protos.DisableAndReadMetatraceResult()
+      result.ParseFromString(f.read())
+      return result
diff --git a/src/trace_processor/python/trace_processor/loader.py b/src/trace_processor/python/perfetto/trace_processor/loader.py
similarity index 83%
rename from src/trace_processor/python/trace_processor/loader.py
rename to src/trace_processor/python/perfetto/trace_processor/loader.py
index 2583561..e57145f 100644
--- a/src/trace_processor/python/trace_processor/loader.py
+++ b/src/trace_processor/python/perfetto/trace_processor/loader.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 import os
+import socket
 import subprocess
 import tempfile
 from urllib import request
@@ -28,6 +29,9 @@
   # URL to download script to run trace_processor
   SHELL_URL = 'http://get.perfetto.dev/trace_processor'
 
+  # Default port that trace_processor_shell runs on
+  TP_PORT = '9001'
+
   def read_tp_descriptor():
     ws = os.path.dirname(__file__)
     with open(os.path.join(ws, 'trace_processor.descriptor'), 'rb') as x:
@@ -64,6 +68,16 @@
         raise Exception('Path to binary is not valid')
       return bin_path
 
+  def get_free_port(unique_port=False):
+    if not unique_port:
+      return LoaderStandalone.TP_PORT, f'localhost:{LoaderStandalone.TP_PORT}'
+    free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    free_socket.bind(('', 0))
+    free_socket.listen(5)
+    port = free_socket.getsockname()[1]
+    free_socket.close()
+    return str(port), f"localhost:{str(port)}"
+
 
 # Return vendor class if it exists before falling back on LoaderStandalone
 def get_loader():
diff --git a/src/trace_processor/python/trace_processor/metrics.descriptor b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
similarity index 85%
rename from src/trace_processor/python/trace_processor/metrics.descriptor
rename to src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
index d8cf017..1ea62c7 100644
--- a/src/trace_processor/python/trace_processor/metrics.descriptor
+++ b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
new file mode 100644
index 0000000..378e501
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -0,0 +1,5 @@
+
+// SHA1(tools/gen_binary_descriptors)
+// e5c244903aa00cad06faf3d126918306a7fe811e
+// SHA1(protos/perfetto/metrics/metrics.proto)
+// e4953b5f61fcf622280cb5f27e2df09a5e85dc40
diff --git a/src/trace_processor/python/trace_processor/protos.py b/src/trace_processor/python/perfetto/trace_processor/protos.py
similarity index 91%
rename from src/trace_processor/python/trace_processor/protos.py
rename to src/trace_processor/python/perfetto/trace_processor/protos.py
index 6321647..b5e3700 100644
--- a/src/trace_processor/python/trace_processor/protos.py
+++ b/src/trace_processor/python/perfetto/trace_processor/protos.py
@@ -55,3 +55,7 @@
     self.RawQueryArgs = create_message_factory('perfetto.protos.RawQueryArgs')
     self.QueryResult = create_message_factory('perfetto.protos.QueryResult')
     self.TraceMetrics = create_message_factory('perfetto.protos.TraceMetrics')
+    self.DisableAndReadMetatraceResult = create_message_factory(
+        'perfetto.protos.DisableAndReadMetatraceResult')
+    self.CellsBatch = create_message_factory(
+        'perfetto.protos.QueryResult.CellsBatch')
diff --git a/src/trace_processor/python/perfetto/trace_processor/shell.py b/src/trace_processor/python/perfetto/trace_processor/shell.py
new file mode 100644
index 0000000..44da342
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/shell.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# 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.
+
+import os
+import subprocess
+import time
+from urllib import request, error
+
+from .loader import get_loader
+
+
+def load_shell(bin_path=None, unique_port=False):
+  shell_path = get_loader().get_shell_path(bin_path=bin_path)
+  port, url = get_loader().get_free_port(unique_port=unique_port)
+  p = subprocess.Popen([shell_path, '-D', '--http-port', port],
+                       stdout=subprocess.DEVNULL)
+
+  while True:
+    try:
+      if p.poll() != None:
+        if unique_port:
+          raise Exception(
+              "Random port allocation failed, please file a bug at https://goto.google.com/perfetto-bug"
+          )
+        raise Exception(
+            "Trace processor failed to start, please file a bug at https://goto.google.com/perfetto-bug"
+        )
+      req = request.urlretrieve(f'http://{url}/status')
+      time.sleep(1)
+      break
+    except error.URLError:
+      pass
+
+  return url, p
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
new file mode 100644
index 0000000..3d2bf28
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
new file mode 100644
index 0000000..98efcd9
--- /dev/null
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -0,0 +1,5 @@
+
+// SHA1(tools/gen_binary_descriptors)
+// e5c244903aa00cad06faf3d126918306a7fe811e
+// SHA1(protos/perfetto/trace_processor/trace_processor.proto)
+// 8320f306d6d5bbcb5ef6ba8cd62cc70a0994d102
diff --git a/src/trace_processor/python/setup.py b/src/trace_processor/python/setup.py
index e2f6a86..c826824 100644
--- a/src/trace_processor/python/setup.py
+++ b/src/trace_processor/python/setup.py
@@ -1,2 +1,29 @@
-# TODO(aninditaghosh): fill this in when we're ready to support
-# local, pip-based installation.
\ No newline at end of file
+from distutils.core import setup
+
+setup(
+    name='perfetto',
+    packages=['perfetto', 'perfetto.trace_processor'],
+    package_data={'perfetto.trace_processor': ['*.descriptor']},
+    include_package_data=True,
+    version='0.2.9',
+    license='apache-2.0',
+    description='Python API for Perfetto\'s Trace Processor',
+    author='Perfetto',
+    author_email='perfetto-pypi@google.com',
+    url='https://perfetto.dev/',
+    download_url='https://github.com/google/perfetto/archive/v6.0.tar.gz',
+    keywords=['trace processor', 'tracing', 'perfetto'],
+    install_requires=[
+        'protobuf',
+    ],
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'License :: OSI Approved :: Apache Software License',
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+    ],
+)
diff --git a/src/trace_processor/python/trace_processor/__init__.py b/src/trace_processor/python/trace_processor/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/trace_processor/python/trace_processor/__init__.py
+++ /dev/null
diff --git a/src/trace_processor/python/trace_processor/api.py b/src/trace_processor/python/trace_processor/api.py
deleted file mode 100644
index 867ea79..0000000
--- a/src/trace_processor/python/trace_processor/api.py
+++ /dev/null
@@ -1,122 +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.
-
-from urllib.parse import urlparse
-
-from .http import TraceProcessorHttp
-from .loader import get_loader
-from .shell import load_shell
-
-class TraceProcessor:
-
-  # Values of these constants correspond to the QueryResponse message at
-  # protos/perfetto/trace_processor/trace_processor.proto
-  # Values 0 and 1 correspond to CELL_INVALID and CELL_NULL respectively,
-  # which are both represented as None in this class's response
-  QUERY_CELL_VARINT_FIELD_ID = 2
-  QUERY_CELL_FLOAT64_FIELD_ID = 3
-  QUERY_CELL_STRING_FIELD_ID = 4
-  QUERY_CELL_BLOB_FIELD_ID = 5
-
-  # This is the class returned to the user and contains one row of the
-  # resultant query. Each column name is stored as an attribute of this
-  # class, with the value corresponding to the column name and row in
-  # the query results table.
-  class Row:
-    pass
-
-  class QueryResultIterator:
-
-    def __init__(self, column_names, batches):
-      # TODO(@aninditaghosh): Revisit to handle multiple batches
-      self.__cells = batches[0].cells
-      self.__varint_cells = batches[0].varint_cells
-      self.__float64_cells = batches[0].float64_cells
-      self.__blob_cells = batches[0].blob_cells
-      # TODO(aninditaghosh): Revisit string cells for larger traces
-      self.__string_cells = batches[0].string_cells.split('\0')
-      self.__column_names = column_names
-
-    def get_cell_list(self, proto_index):
-      if proto_index == TraceProcessor.QUERY_CELL_VARINT_FIELD_ID:
-        return self.__varint_cells
-      elif proto_index == TraceProcessor.QUERY_CELL_FLOAT64_FIELD_ID:
-        return self.__float64_cells
-      elif proto_index == TraceProcessor.QUERY_CELL_STRING_FIELD_ID:
-        return self.__string_cells
-      elif proto_index == TraceProcessor.QUERY_CELL_BLOB_FIELD_ID:
-        return self.__blob_cells
-      else:
-        return None
-
-    def __iter__(self):
-      self.__next_index = 0
-      return self
-
-    def __next__(self):
-      if self.__next_index >= len(self.__cells):
-        raise StopIteration
-      row = TraceProcessor.Row()
-      for num, column_name in enumerate(self.__column_names):
-        cell_list = self.get_cell_list(self.__cells[self.__next_index + num])
-        if cell_list is not None:
-          val = cell_list.pop(0)
-        setattr(row, column_name, val or None)
-      self.__next_index = self.__next_index + len(self.__column_names)
-      return row
-
-  def __init__(self, addr=None, file_path=None, bin_path=None):
-    # Load trace_processor_shell or access via given address
-    if addr:
-      p = urlparse(addr)
-      tp = TraceProcessorHttp(p.netloc if p.netloc else p.path)
-    else:
-      url, self.subprocess = load_shell(bin_path=bin_path)
-      tp = TraceProcessorHttp(url)
-    self.http = tp
-
-    # Parse trace by its file_path into the loaded instance of trace_processor
-    if file_path:
-      get_loader().parse_file(self.http, file_path)
-
-  def query(self, sql):
-    """Executes passed in SQL query using class defined HTTP API, and returns
-    the response as a QueryResultIterator
-
-    Args:
-      sql: SQL query written as a String
-
-    Returns:
-      A class which can iterate through each row of the results table
-    """
-    response = self.http.execute_query(sql)
-    return TraceProcessor.QueryResultIterator(response.column_names,
-                                              response.batch)
-
-  def metric(self, metrics):
-    """Returns the metrics data corresponding to the passed in trace metric.
-
-    Args:
-      metrics: A list of valid metrics as defined in TraceMetrics
-
-    Returns:
-      The metrics data as a proto message
-    """
-    return self.http.compute_metric(metrics)
-
-  # TODO(@aninditaghosh): Investigate context managers for
-  # cleaner usage
-  def close(self):
-    if hasattr(self, 'subprocess'):
-      self.subprocess.kill()
diff --git a/src/trace_processor/python/trace_processor/metrics.descriptor.sha1 b/src/trace_processor/python/trace_processor/metrics.descriptor.sha1
deleted file mode 100644
index 0e36f48..0000000
--- a/src/trace_processor/python/trace_processor/metrics.descriptor.sha1
+++ /dev/null
@@ -1,5 +0,0 @@
-
-// SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
-// SHA1(protos/perfetto/metrics/metrics.proto)
-// f5156fc72421bae158c30ed65e3a322c276ae805
diff --git a/src/trace_processor/python/trace_processor/shell.py b/src/trace_processor/python/trace_processor/shell.py
deleted file mode 100644
index 632e78f..0000000
--- a/src/trace_processor/python/trace_processor/shell.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-import os
-import subprocess
-import time
-from urllib import request, error
-
-from .loader import get_loader
-
-
-def load_shell(bin_path=None):
-  shell_path = get_loader().get_shell_path(bin_path=bin_path)
-  p = subprocess.Popen([shell_path, '-D'], stdout=subprocess.DEVNULL)
-
-  while True:
-    try:
-      req = request.urlretrieve('http://localhost:9001/status')
-      time.sleep(1)
-      break
-    except error.URLError:
-      pass
-  return 'localhost:9001', p
diff --git a/src/trace_processor/python/trace_processor/trace_processor.descriptor b/src/trace_processor/python/trace_processor/trace_processor.descriptor
deleted file mode 100644
index 69a2f90..0000000
--- a/src/trace_processor/python/trace_processor/trace_processor.descriptor
+++ /dev/null
Binary files differ
diff --git a/src/trace_processor/python/trace_processor/trace_processor.descriptor.sha1 b/src/trace_processor/python/trace_processor/trace_processor.descriptor.sha1
deleted file mode 100644
index 9e90273..0000000
--- a/src/trace_processor/python/trace_processor/trace_processor.descriptor.sha1
+++ /dev/null
@@ -1,5 +0,0 @@
-
-// SHA1(tools/gen_binary_descriptors)
-// 6deed7c8efd4c9f8450c38a2560e8844bbbd6ea8
-// SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// 9bba60ed823eaa82c039f89826a764161c18a3ad
diff --git a/src/trace_processor/read_trace.cc b/src/trace_processor/read_trace.cc
index 3a655c6..8f5b6f0 100644
--- a/src/trace_processor/read_trace.cc
+++ b/src/trace_processor/read_trace.cc
@@ -16,17 +16,24 @@
 
 #include "perfetto/trace_processor/read_trace.h"
 
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/utils.h"
+#include "perfetto/protozero/proto_utils.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
+#include "src/trace_processor/forwarding_trace_parser.h"
+#include "src/trace_processor/importers/gzip/gzip_trace_parser.h"
 #include "src/trace_processor/importers/gzip/gzip_utils.h"
+#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/util/status_macros.h"
 
 #include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #define PERFETTO_HAS_AIO_H() 1
 #else
 #define PERFETTO_HAS_AIO_H() 0
@@ -38,6 +45,70 @@
 
 namespace perfetto {
 namespace trace_processor {
+namespace {
+
+// 1MB chunk size seems the best tradeoff on a MacBook Pro 2013 - i7 2.8 GHz.
+constexpr size_t kChunkSize = 1024 * 1024;
+
+util::Status ReadTraceUsingRead(
+    TraceProcessor* tp,
+    int fd,
+    uint64_t* file_size,
+    const std::function<void(uint64_t parsed_size)>& progress_callback) {
+  // Load the trace in chunks using ordinary read().
+  for (int i = 0;; i++) {
+    if (progress_callback && i % 128 == 0)
+      progress_callback(*file_size);
+
+    std::unique_ptr<uint8_t[]> buf(new uint8_t[kChunkSize]);
+    auto rsize = base::Read(fd, buf.get(), kChunkSize);
+    if (rsize == 0)
+      break;
+
+    if (rsize < 0) {
+      return util::ErrStatus("Reading trace file failed (errno: %d, %s)", errno,
+                             strerror(errno));
+    }
+
+    *file_size += static_cast<uint64_t>(rsize);
+
+    RETURN_IF_ERROR(tp->Parse(std::move(buf), static_cast<size_t>(rsize)));
+  }
+  return util::OkStatus();
+}
+
+class SerializingProtoTraceReader : public ChunkedTraceReader {
+ public:
+  SerializingProtoTraceReader(std::vector<uint8_t>* output) : output_(output) {}
+
+  util::Status Parse(std::unique_ptr<uint8_t[]> data, size_t size) override {
+    return tokenizer_.Tokenize(
+        std::move(data), size, [this](TraceBlobView packet) {
+          uint8_t buffer[protozero::proto_utils::kMaxSimpleFieldEncodedSize];
+
+          uint8_t* pos = buffer;
+          pos = protozero::proto_utils::WriteVarInt(kTracePacketTag, pos);
+          pos = protozero::proto_utils::WriteVarInt(packet.length(), pos);
+          output_->insert(output_->end(), buffer, pos);
+
+          output_->insert(output_->end(), packet.data(),
+                          packet.data() + packet.length());
+          return util::OkStatus();
+        });
+  }
+
+  void NotifyEndOfFile() override {}
+
+ private:
+  static constexpr uint8_t kTracePacketTag =
+      protozero::proto_utils::MakeTagLengthDelimited(
+          protos::pbzero::Trace::kPacketFieldNumber);
+
+  ProtoTraceTokenizer tokenizer_;
+  std::vector<uint8_t>* output_;
+};
+
+}  // namespace
 
 util::Status ReadTrace(
     TraceProcessor* tp,
@@ -47,8 +118,6 @@
   if (!fd)
     return util::ErrStatus("Could not open trace file (path: %s)", filename);
 
-  // 1MB chunk size seems the best tradeoff on a MacBook Pro 2013 - i7 2.8 GHz.
-  constexpr size_t kChunkSize = 1024 * 1024;
   uint64_t file_size = 0;
 
 #if PERFETTO_HAS_AIO_H()
@@ -95,27 +164,17 @@
     PERFETTO_CHECK(aio_read(&cb) == 0);
 
     // Parse the completed buffer while the async read is in-flight.
-    util::Status status = tp->Parse(std::move(buf), static_cast<size_t>(rsize));
-    if (PERFETTO_UNLIKELY(!status.ok()))
-      return status;
+    RETURN_IF_ERROR(tp->Parse(std::move(buf), static_cast<size_t>(rsize)));
+  }
+
+  if (file_size == 0) {
+    PERFETTO_ILOG(
+        "Failed to read any data using AIO. This is expected and not an error "
+        "on WSL. Falling back to read()");
+    RETURN_IF_ERROR(ReadTraceUsingRead(tp, *fd, &file_size, progress_callback));
   }
 #else   // PERFETTO_HAS_AIO_H()
-  // Load the trace in chunks using ordinary read().
-  // This version is used on Windows, since there's no aio library.
-  for (int i = 0;; i++) {
-    if (progress_callback && i % 128 == 0)
-      progress_callback(file_size);
-
-    std::unique_ptr<uint8_t[]> buf(new uint8_t[kChunkSize]);
-    auto rsize = read(*fd, buf.get(), kChunkSize);
-    if (rsize <= 0)
-      break;
-    file_size += static_cast<uint64_t>(rsize);
-
-    util::Status status = tp->Parse(std::move(buf), static_cast<size_t>(rsize));
-    if (PERFETTO_UNLIKELY(!status.ok()))
-      return status;
-  }
+  RETURN_IF_ERROR(ReadTraceUsingRead(tp, *fd, &file_size, progress_callback));
 #endif  // PERFETTO_HAS_AIO_H()
 
   tp->NotifyEndOfFile();
@@ -129,13 +188,32 @@
 util::Status DecompressTrace(const uint8_t* data,
                              size_t size,
                              std::vector<uint8_t>* output) {
-  if (!gzip::IsGzipSupported()) {
+  TraceType type = GuessTraceType(data, size);
+  if (type != TraceType::kGzipTraceType && type != TraceType::kProtoTraceType) {
     return util::ErrStatus(
-        "Cannot decompress trace in build where zlib is disabled");
+        "Only GZIP and proto trace types are supported by DecompressTrace");
   }
 
+  if (type == TraceType::kGzipTraceType) {
+    std::unique_ptr<ChunkedTraceReader> reader(
+        new SerializingProtoTraceReader(output));
+    GzipTraceParser parser(std::move(reader));
+
+    RETURN_IF_ERROR(parser.ParseUnowned(data, size));
+    if (parser.needs_more_input())
+      return util::ErrStatus("Cannot decompress partial trace file");
+
+    parser.NotifyEndOfFile();
+    return util::OkStatus();
+  }
+
+  PERFETTO_CHECK(type == TraceType::kProtoTraceType);
+
   protos::pbzero::Trace::Decoder decoder(data, size);
   GzipDecompressor decompressor;
+  if (size > 0 && !decoder.packet()) {
+    return util::ErrStatus("Trace does not contain valid packets");
+  }
   for (auto it = decoder.packet(); it; ++it) {
     protos::pbzero::TracePacket::Decoder packet(*it);
     if (!packet.has_compressed_packets()) {
diff --git a/src/trace_processor/read_trace_integrationtest.cc b/src/trace_processor/read_trace_integrationtest.cc
index 5070e35..c4218af 100644
--- a/src/trace_processor/read_trace_integrationtest.cc
+++ b/src/trace_processor/read_trace_integrationtest.cc
@@ -28,10 +28,12 @@
 namespace trace_processor {
 namespace {
 
-TEST(ReadTraceIntegrationTest, CompressedTrace) {
-  base::ScopedFstream f(fopen(
-      base::GetTestDataPath(std::string("test/data/compressed.pb")).c_str(),
-      "rb"));
+base::ScopedFstream OpenTestTrace(const std::string& path) {
+  std::string full_path = base::GetTestDataPath(path);
+  return base::ScopedFstream(fopen(full_path.c_str(), "rb"));
+}
+
+std::vector<uint8_t> ReadAllData(const base::ScopedFstream& f) {
   std::vector<uint8_t> raw_trace;
   while (!feof(*f)) {
     uint8_t buf[4096];
@@ -39,6 +41,12 @@
         fread(reinterpret_cast<char*>(buf), 1, base::ArraySize(buf), *f);
     raw_trace.insert(raw_trace.end(), buf, buf + rsize);
   }
+  return raw_trace;
+}
+
+TEST(ReadTraceIntegrationTest, CompressedTrace) {
+  base::ScopedFstream f = OpenTestTrace("test/data/compressed.pb");
+  std::vector<uint8_t> raw_trace = ReadAllData(f);
 
   std::vector<uint8_t> decompressed;
   decompressed.reserve(raw_trace.size());
@@ -58,6 +66,54 @@
   ASSERT_EQ(packet_count, 2412u);
 }
 
+TEST(ReadTraceIntegrationTest, NonProtobufShouldNotDecompress) {
+  base::ScopedFstream f = OpenTestTrace("test/data/unsorted_trace.json");
+  std::vector<uint8_t> raw_trace = ReadAllData(f);
+
+  std::vector<uint8_t> decompressed;
+  util::Status status = trace_processor::DecompressTrace(
+      raw_trace.data(), raw_trace.size(), &decompressed);
+  ASSERT_FALSE(status.ok());
+}
+
+TEST(ReadTraceIntegrationTest, OuterGzipDecompressTrace) {
+  base::ScopedFstream f =
+      OpenTestTrace("test/data/example_android_trace_30s.pb.gz");
+  std::vector<uint8_t> raw_compressed_trace = ReadAllData(f);
+
+  std::vector<uint8_t> decompressed;
+  util::Status status = trace_processor::DecompressTrace(
+      raw_compressed_trace.data(), raw_compressed_trace.size(), &decompressed);
+  ASSERT_TRUE(status.ok());
+
+  base::ScopedFstream u =
+      OpenTestTrace("test/data/example_android_trace_30s.pb");
+  std::vector<uint8_t> raw_trace = ReadAllData(u);
+
+  ASSERT_EQ(decompressed.size(), raw_trace.size());
+  ASSERT_EQ(decompressed, raw_trace);
+}
+
+TEST(ReadTraceIntegrationTest, DoubleGzipDecompressTrace) {
+  base::ScopedFstream f = OpenTestTrace("test/data/compressed.pb.gz");
+  std::vector<uint8_t> raw_compressed_trace = ReadAllData(f);
+
+  std::vector<uint8_t> decompressed;
+  util::Status status = trace_processor::DecompressTrace(
+      raw_compressed_trace.data(), raw_compressed_trace.size(), &decompressed);
+  ASSERT_TRUE(status.ok());
+
+  protos::pbzero::Trace::Decoder decoder(decompressed.data(),
+                                         decompressed.size());
+  uint32_t packet_count = 0;
+  for (auto it = decoder.packet(); it; ++it) {
+    protos::pbzero::TracePacket::Decoder packet(*it);
+    ASSERT_FALSE(packet.has_compressed_packets());
+    ++packet_count;
+  }
+  ASSERT_EQ(packet_count, 2412u);
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/rpc/httpd.cc b/src/trace_processor/rpc/httpd.cc
index 4d4d935..1a061aa 100644
--- a/src/trace_processor/rpc/httpd.cc
+++ b/src/trace_processor/rpc/httpd.cc
@@ -39,7 +39,8 @@
 
 namespace {
 
-constexpr char kBindAddr[] = "127.0.0.1:9001";
+constexpr char kBindPort[] = "9001";
+constexpr size_t kOmitContentLength = static_cast<size_t>(-1);
 
 // 32 MiB payload + 128K for HTTP headers.
 constexpr size_t kMaxRequestSize = (32 * 1024 + 128) * 1024;
@@ -68,7 +69,7 @@
  public:
   explicit HttpServer(std::unique_ptr<TraceProcessor>);
   ~HttpServer() override;
-  void Run();
+  void Run(const char*, const char*);
 
  private:
   size_t ParseOneHttpRequest(Client* client);
@@ -82,7 +83,8 @@
 
   Rpc trace_processor_rpc_;
   base::UnixTaskRunner task_runner_;
-  std::unique_ptr<base::UnixSocket> sock_;
+  std::unique_ptr<base::UnixSocket> sock4_;
+  std::unique_ptr<base::UnixSocket> sock6_;
   std::vector<Client> clients_;
 };
 
@@ -97,8 +99,8 @@
 void HttpReply(base::UnixSocket* sock,
                const char* http_code,
                std::initializer_list<const char*> headers = {},
-               const uint8_t* body = nullptr,
-               size_t body_len = 0) {
+               const uint8_t* content = nullptr,
+               size_t content_length = 0) {
   std::vector<char> response;
   response.reserve(4096);
   Append(response, "HTTP/1.1 ");
@@ -108,12 +110,15 @@
     Append(response, hdr);
     Append(response, "\r\n");
   }
-  Append(response, "Content-Length: ");
-  Append(response, std::to_string(body_len));
-  Append(response, "\r\n\r\n");  // End-of-headers marker.
-  sock->Send(response.data(), response.size());
-  if (body_len)
-    sock->Send(body, body_len);
+  if (content_length != kOmitContentLength) {
+    Append(response, "Content-Length: ");
+    Append(response, std::to_string(content_length));
+    Append(response, "\r\n");
+  }
+  Append(response, "\r\n");                      // End-of-headers marker.
+  sock->Send(response.data(), response.size());  // Send response headers.
+  if (content_length > 0 && content_length != kOmitContentLength)
+    sock->Send(content, content_length);  // Send response payload.
 }
 
 void ShutdownBadRequest(base::UnixSocket* sock, const char* reason) {
@@ -126,11 +131,28 @@
     : trace_processor_rpc_(std::move(preloaded_instance)) {}
 HttpServer::~HttpServer() = default;
 
-void HttpServer::Run() {
-  PERFETTO_ILOG("[HTTP] Starting RPC server on %s", kBindAddr);
-  sock_ = base::UnixSocket::Listen(kBindAddr, this, &task_runner_,
-                                   base::SockFamily::kInet,
-                                   base::SockType::kStream);
+void HttpServer::Run(const char* kBindAddr4, const char* kBindAddr6) {
+  PERFETTO_ILOG("[HTTP] Starting RPC server on %s and %s", kBindAddr4,
+                kBindAddr6);
+
+  sock4_ = base::UnixSocket::Listen(kBindAddr4, this, &task_runner_,
+                                    base::SockFamily::kInet,
+                                    base::SockType::kStream);
+  bool ipv4_listening = sock4_ && sock4_->is_listening();
+  if (!ipv4_listening) {
+    PERFETTO_ILOG("Failed to listen on IPv4 socket");
+  }
+
+  sock6_ = base::UnixSocket::Listen(kBindAddr6, this, &task_runner_,
+                                    base::SockFamily::kInet6,
+                                    base::SockType::kStream);
+  bool ipv6_listening = sock6_ && sock6_->is_listening();
+  if (!ipv6_listening) {
+    PERFETTO_ILOG("Failed to listen on IPv6 socket");
+  }
+
+  PERFETTO_CHECK(ipv4_listening || ipv6_listening);
+
   task_runner_.Run();
 }
 
@@ -256,11 +278,15 @@
                req.body.size());
   std::string allow_origin_hdr =
       "Access-Control-Allow-Origin: " + req.origin.ToStdString();
+
+  // This is the default. Overridden by the /query handler for chunked replies.
+  char transfer_encoding_hdr[255] = "Transfer-Encoding: identity";
   std::initializer_list<const char*> headers = {
       "Connection: Keep-Alive",                //
       "Cache-Control: no-cache",               //
       "Keep-Alive: timeout=5, max=1000",       //
       "Content-Type: application/x-protobuf",  //
+      transfer_encoding_hdr,                   //
       allow_origin_hdr.c_str()};
 
   if (req.method == "OPTIONS") {
@@ -290,17 +316,43 @@
     return HttpReply(client->sock.get(), "200 OK", headers);
   }
 
+  // New endpoint, returns data in batches using chunked transfer encoding.
+  // The batch size is determined by |cells_per_batch_| and
+  // |batch_split_threshold_| in query_result_serializer.h.
+  // This is temporary, it will be switched to WebSockets soon.
   if (req.uri == "/query") {
-    // TODO(primiano): implement chunking here.
-    PERFETTO_CHECK(req.body.size() > 0u);
-    std::vector<uint8_t> response = trace_processor_rpc_.Query(
-        reinterpret_cast<const uint8_t*>(req.body.data()), req.body.size());
-    return HttpReply(client->sock.get(), "200 OK", headers, response.data(),
-                     response.size());
+    std::vector<uint8_t> response;
+
+    // Start the chunked reply.
+    strncpy(transfer_encoding_hdr, "Transfer-Encoding: chunked",
+            sizeof(transfer_encoding_hdr));
+    base::UnixSocket* cli_sock = client->sock.get();
+    HttpReply(cli_sock, "200 OK", headers, nullptr, kOmitContentLength);
+
+    // |on_result_chunk| will be called nested within the same callstack of the
+    // rpc.Query() call. No further calls will be made once Query() returns.
+    auto on_result_chunk = [&](const uint8_t* buf, size_t len, bool has_more) {
+      PERFETTO_DLOG("Sending response chunk, len=%zu eof=%d", len, !has_more);
+      char chunk_hdr[32];
+      auto hdr_len = static_cast<size_t>(sprintf(chunk_hdr, "%zx\r\n", len));
+      cli_sock->Send(chunk_hdr, hdr_len);
+      cli_sock->Send(buf, len);
+      cli_sock->Send("\r\n", 2);
+      if (!has_more) {
+        hdr_len = static_cast<size_t>(sprintf(chunk_hdr, "0\r\n\r\n"));
+        cli_sock->Send(chunk_hdr, hdr_len);
+      }
+    };
+    trace_processor_rpc_.Query(
+        reinterpret_cast<const uint8_t*>(req.body.data()), req.body.size(),
+        on_result_chunk);
+    return;
   }
 
+  // Legacy endpoint.
+  // Returns a columnar-oriented one-shot result. Very inefficient for large
+  // result sets. Very inefficient in general too.
   if (req.uri == "/raw_query") {
-    PERFETTO_CHECK(req.body.size() > 0u);
     std::vector<uint8_t> response = trace_processor_rpc_.RawQuery(
         reinterpret_cast<const uint8_t*>(req.body.data()), req.body.size());
     return HttpReply(client->sock.get(), "200 OK", headers, response.data(),
@@ -323,6 +375,13 @@
                      res.size());
   }
 
+  if (req.uri == "/get_metric_descriptors") {
+    std::vector<uint8_t> res = trace_processor_rpc_.GetMetricDescriptors(
+        reinterpret_cast<const uint8_t*>(req.body.data()), req.body.size());
+    return HttpReply(client->sock.get(), "200 OK", headers, res.data(),
+                     res.size());
+  }
+
   if (req.uri == "/enable_metatrace") {
     trace_processor_rpc_.EnableMetatrace();
     return HttpReply(client->sock.get(), "200 OK", headers);
@@ -339,9 +398,13 @@
 
 }  // namespace
 
-void RunHttpRPCServer(std::unique_ptr<TraceProcessor> preloaded_instance) {
+void RunHttpRPCServer(std::unique_ptr<TraceProcessor> preloaded_instance,
+                      std::string port_number) {
   HttpServer srv(std::move(preloaded_instance));
-  srv.Run();
+  std::string port = port_number.empty() ? kBindPort : port_number;
+  std::string ipv4_addr = "127.0.0.1:" + port;
+  std::string ipv6_addr = "[::1]:" + port;
+  srv.Run(ipv4_addr.c_str(), ipv6_addr.c_str());
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/rpc/httpd.h b/src/trace_processor/rpc/httpd.h
index b145da8..09054e8 100644
--- a/src/trace_processor/rpc/httpd.h
+++ b/src/trace_processor/rpc/httpd.h
@@ -29,7 +29,7 @@
 // The unique_ptr argument is optional. If non-null, the HTTP server will adopt
 // an existing instance with a pre-loaded trace. If null, it will create a new
 // instance when pushing data into the /parse endpoint.
-void RunHttpRPCServer(std::unique_ptr<TraceProcessor>);
+void RunHttpRPCServer(std::unique_ptr<TraceProcessor>, std::string);
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/rpc/query_result_serializer.cc b/src/trace_processor/rpc/query_result_serializer.cc
index 3965efa..52fdb07 100644
--- a/src/trace_processor/rpc/query_result_serializer.cc
+++ b/src/trace_processor/rpc/query_result_serializer.cc
@@ -113,16 +113,19 @@
   uint32_t cell_idx = 0;
   bool batch_full = false;
 
-  // Skip block if the query didn't return any result (e.g. CREATE TABLE).
-  for (; num_cols_ > 0; ++cell_idx, ++col_) {
+  for (;; ++cell_idx, ++col_) {
     // This branch is hit before starting each row. Note that iter_->Next() must
     // be called before iterating on a row. col_ is initialized at MAX_INT in
     // the constructor.
     if (col_ >= num_cols_) {
       col_ = 0;
+      // If num_cols_ == 0 and the query didn't return any result (e.g. CREATE
+      // TABLE) we should exit at this point. We still need to advance the
+      // iterator via Next() otherwise the statement will have no effect.
       if (!iter_->Next())
         break;  // EOF or error.
 
+      PERFETTO_DCHECK(num_cols_ > 0);
       // We need to guarantee that a batch contains whole rows. Before moving to
       // the next row, make sure that: (i) there is space for all the columns;
       // (ii) the batch didn't grow too much.
@@ -162,7 +165,7 @@
             static_cast<uint32_t>(strlen(value.string_value)) + 1;
         const char* str_begin = value.string_value;
         buf->insert(buf->end(), str_begin, str_begin + len_with_nul);
-        approx_batch_size += len_with_nul;
+        approx_batch_size += len_with_nul + 4;  // 4 is a guess on the preamble.
         break;
       }
       case SqlValue::Type::kBytes: {
diff --git a/src/trace_processor/rpc/query_result_serializer.h b/src/trace_processor/rpc/query_result_serializer.h
index b0f3df1..f7a7aaa 100644
--- a/src/trace_processor/rpc/query_result_serializer.h
+++ b/src/trace_processor/rpc/query_result_serializer.h
@@ -75,9 +75,14 @@
   bool eof_reached_ = false;
   uint32_t col_ = UINT32_MAX;
 
+  // These params specify the thresholds for splitting the results in batches,
+  // in terms of: (1) max cells (row x cols); (2) serialized batch size in
+  // bytes, whichever is reached first. Note also that the byte limit is not
+  // 100% accurate and can occasionally yield to batches slighly larger than
+  // the limit (it splits on the next row *after* the limit is hit).
   // Overridable for testing only.
-  uint32_t cells_per_batch_ = 2048;
-  uint32_t batch_split_threshold_ = 1024 * 32;
+  uint32_t cells_per_batch_ = 50000;
+  uint32_t batch_split_threshold_ = 1024 * 128;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/rpc/query_result_serializer_unittest.cc b/src/trace_processor/rpc/query_result_serializer_unittest.cc
index e537c99..a655410 100644
--- a/src/trace_processor/rpc/query_result_serializer_unittest.cc
+++ b/src/trace_processor/rpc/query_result_serializer_unittest.cc
@@ -185,7 +185,7 @@
 
       EXPECT_FALSE(parse_error);
     }
-    if (columns.size() == 0) {
+    if (columns.empty()) {
       EXPECT_EQ(num_cells, 0u);
     } else {
       EXPECT_EQ(num_cells % columns.size(), 0u);
@@ -428,13 +428,26 @@
 
 TEST(QueryResultSerializerTest, NoResultQuery) {
   auto tp = TraceProcessor::CreateInstance(trace_processor::Config());
-  auto iter = tp->ExecuteQuery("create table tab (x)");
-  QueryResultSerializer ser(std::move(iter));
-  TestDeserializer deser;
-  deser.SerializeAndDeserialize(&ser);
-  EXPECT_EQ(deser.error, "");
-  EXPECT_EQ(deser.cells.size(), 0u);
-  EXPECT_TRUE(deser.eof_reached);
+  {
+    auto iter = tp->ExecuteQuery("create table tab (x)");
+    QueryResultSerializer ser(std::move(iter));
+    TestDeserializer deser;
+    deser.SerializeAndDeserialize(&ser);
+    EXPECT_EQ(deser.error, "");
+    EXPECT_EQ(deser.cells.size(), 0u);
+    EXPECT_TRUE(deser.eof_reached);
+  }
+
+  // Check that the table has been created for real.
+  {
+    auto iter = tp->ExecuteQuery("select count(*) from tab");
+    QueryResultSerializer ser(std::move(iter));
+    TestDeserializer deser;
+    deser.SerializeAndDeserialize(&ser);
+    EXPECT_EQ(deser.error, "");
+    EXPECT_EQ(deser.cells.size(), 1u);
+    EXPECT_TRUE(deser.eof_reached);
+  }
 }
 
 }  // namespace
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 5cf38f6..7a9a595 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -85,7 +85,9 @@
   }
 }
 
-std::vector<uint8_t> Rpc::Query(const uint8_t* args, size_t len) {
+void Rpc::Query(const uint8_t* args,
+                size_t len,
+                QueryResultBatchCallback result_callback) {
   protos::pbzero::RawQueryArgs::Decoder query(args, len);
   std::string sql = query.sql_query().ToStdString();
   PERFETTO_DLOG("[RPC] Query < %s", sql.c_str());
@@ -97,18 +99,20 @@
     PERFETTO_ELOG("[RPC] %s", kErr);
     protozero::HeapBuffered<protos::pbzero::QueryResult> result;
     result->set_error(kErr);
-    return result.SerializeAsArray();
+    auto vec = result.SerializeAsArray();
+    result_callback(vec.data(), vec.size(), /*has_more=*/false);
+    return;
   }
 
   auto it = trace_processor_->ExecuteQuery(sql.c_str());
   QueryResultSerializer serializer(std::move(it));
 
-  // TODO(primiano): propagate chunks instead of piling up batches in the same
-  // result.
   std::vector<uint8_t> res;
-  while (serializer.Serialize(&res)) {
+  for (bool has_more = true; has_more;) {
+    has_more = serializer.Serialize(&res);
+    result_callback(res.data(), res.size(), has_more);
+    res.clear();
   }
-  return res;
 }
 
 std::vector<uint8_t> Rpc::RawQuery(const uint8_t* args, size_t len) {
@@ -271,22 +275,55 @@
   PERFETTO_TP_TRACE("RPC_COMPUTE_METRIC", [&](metatrace::Record* r) {
     for (const auto& metric : metric_names) {
       r->AddArg("Metric", metric);
+      r->AddArg("Format", std::to_string(args.format()));
     }
   });
 
-  std::vector<uint8_t> metrics_proto;
-  util::Status status =
-      trace_processor_->ComputeMetric(metric_names, &metrics_proto);
-  if (status.ok()) {
-    result->AppendBytes(
-        protos::pbzero::ComputeMetricResult::kMetricsFieldNumber,
-        metrics_proto.data(), metrics_proto.size());
-  } else {
-    result->set_error(status.message());
+  switch (args.format()) {
+    case protos::pbzero::ComputeMetricArgs::BINARY_PROTOBUF: {
+      std::vector<uint8_t> metrics_proto;
+      util::Status status =
+          trace_processor_->ComputeMetric(metric_names, &metrics_proto);
+      if (status.ok()) {
+        result->AppendBytes(
+            protos::pbzero::ComputeMetricResult::kMetricsFieldNumber,
+            metrics_proto.data(), metrics_proto.size());
+      } else {
+        result->set_error(status.message());
+      }
+      break;
+    }
+    case protos::pbzero::ComputeMetricArgs::TEXTPROTO: {
+      std::string metrics_string;
+      util::Status status = trace_processor_->ComputeMetricText(
+          metric_names, TraceProcessor::MetricResultFormat::kProtoText,
+          &metrics_string);
+      if (status.ok()) {
+        result->AppendString(
+            protos::pbzero::ComputeMetricResult::kMetricsAsPrototextFieldNumber,
+            metrics_string);
+      } else {
+        result->set_error(status.message());
+      }
+      break;
+    }
   }
   return result.SerializeAsArray();
 }
 
+std::vector<uint8_t> Rpc::GetMetricDescriptors(const uint8_t*, size_t) {
+  protozero::HeapBuffered<protos::pbzero::GetMetricDescriptorsResult> result;
+  if (!trace_processor_) {
+    return result.SerializeAsArray();
+  }
+  std::vector<uint8_t> descriptor_set =
+      trace_processor_->GetMetricDescriptors();
+  result->AppendBytes(
+      protos::pbzero::GetMetricDescriptorsResult::kDescriptorSetFieldNumber,
+      descriptor_set.data(), descriptor_set.size());
+  return result.SerializeAsArray();
+}
+
 void Rpc::EnableMetatrace() {
   if (!trace_processor_)
     return;
diff --git a/src/trace_processor/rpc/rpc.h b/src/trace_processor/rpc/rpc.h
index 286a12c..bddc1aa 100644
--- a/src/trace_processor/rpc/rpc.h
+++ b/src/trace_processor/rpc/rpc.h
@@ -17,6 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_RPC_RPC_H_
 #define SRC_TRACE_PROCESSOR_RPC_RPC_H_
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -58,13 +59,30 @@
 
   util::Status Parse(const uint8_t* data, size_t len);
   void NotifyEndOfFile();
-  std::vector<uint8_t> Query(const uint8_t* args, size_t len);
   void RestoreInitialTables();
   std::string GetCurrentTraceName();
   std::vector<uint8_t> ComputeMetric(const uint8_t* data, size_t len);
+  std::vector<uint8_t> GetMetricDescriptors(const uint8_t* data, size_t len);
   void EnableMetatrace();
   std::vector<uint8_t> DisableAndReadMetatrace();
 
+  // Runs a query and returns results in batch. Each batch is a proto-encoded
+  // TraceProcessor.QueryResult message and contains a variable number of rows.
+  // The callbacks are called inline, so the whole callstack looks as follows:
+  // Query(..., callback)
+  //   callback(..., has_more=true)
+  //   ...
+  //   callback(..., has_more=false)
+  //   (Query() returns at this point).
+  // TODO(primiano): long-term this API should change and be turned into a
+  // bidirectional streaming api (see go/imperative-metrics). The problem with
+  // the current design is that it holds the callstack until the query is done
+  // and makes nested query hard as they cause re-entrancy. It's okay for now
+  // but will change soon.
+  using QueryResultBatchCallback = std::function<
+      void(const uint8_t* /*buf*/, size_t /*len*/, bool /*has_more*/)>;
+  void Query(const uint8_t* args, size_t len, QueryResultBatchCallback);
+
   // DEPRECATED, only for legacy clients. Use |Query()| above.
   std::vector<uint8_t> RawQuery(const uint8_t* args, size_t len);
 
diff --git a/src/trace_processor/rpc/wasm_bridge.cc b/src/trace_processor/rpc/wasm_bridge.cc
index 80beff1..8c5fb1c 100644
--- a/src/trace_processor/rpc/wasm_bridge.cc
+++ b/src/trace_processor/rpc/wasm_bridge.cc
@@ -97,6 +97,14 @@
           static_cast<uint32_t>(res.size()));
 }
 
+void EMSCRIPTEN_KEEPALIVE trace_processor_get_metric_descriptors(uint32_t);
+void trace_processor_get_metric_descriptors(uint32_t size) {
+  std::vector<uint8_t> res =
+      g_trace_processor_rpc->GetMetricDescriptors(g_req_buf, size);
+  g_reply(reinterpret_cast<const char*>(res.data()),
+          static_cast<uint32_t>(res.size()));
+}
+
 void EMSCRIPTEN_KEEPALIVE trace_processor_enable_metatrace(uint32_t);
 void trace_processor_enable_metatrace(uint32_t) {
   g_trace_processor_rpc->EnableMetatrace();
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index ba2c13f..52352b7 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -61,6 +61,7 @@
       "query_constraints_unittest.cc",
       "span_join_operator_table_unittest.cc",
       "sqlite3_str_split_unittest.cc",
+      "sqlite_utils_unittest.cc",
     ]
     deps = [
       ":sqlite",
diff --git a/src/trace_processor/sqlite/span_join_operator_table.cc b/src/trace_processor/sqlite/span_join_operator_table.cc
index 1357ddf..1d84f37 100644
--- a/src/trace_processor/sqlite/span_join_operator_table.cc
+++ b/src/trace_processor/sqlite/span_join_operator_table.cc
@@ -311,7 +311,11 @@
         desc.name.c_str());
   }
 
-  auto cols = sqlite_utils::GetColumnsForTable(db_, desc.name);
+  std::vector<SqliteTable::Column> cols;
+  auto status = sqlite_utils::GetColumnsForTable(db_, desc.name, cols);
+  if (!status.ok()) {
+    return status;
+  }
 
   uint32_t required_columns_found = 0;
   uint32_t ts_idx = std::numeric_limits<uint32_t>::max();
@@ -383,11 +387,16 @@
                                           FilterHistory) {
   PERFETTO_TP_TRACE("SPAN_JOIN_XFILTER");
 
-  util::Status status = t1_.Initialize(qc, argv);
+  util::Status status =
+      t1_.Initialize(qc, argv, Query::InitialEofBehavior::kTreatAsEof);
   if (!status.ok())
     return SQLITE_ERROR;
 
-  status = t2_.Initialize(qc, argv);
+  status = t2_.Initialize(
+      qc, argv,
+      table_->IsLeftJoin()
+          ? Query::InitialEofBehavior::kTreatAsMissingPartitionShadow
+          : Query::InitialEofBehavior::kTreatAsEof);
   if (!status.ok())
     return SQLITE_ERROR;
 
@@ -465,7 +474,7 @@
     // Find which slice finishes first.
     next_query_ = FindEarliestFinishQuery();
 
-    // If the current span is overlapping, just finsh there to emit the current
+    // If the current span is overlapping, just finish there to emit the current
     // slice.
     if (IsOverlappingSpan())
       break;
@@ -577,11 +586,19 @@
 
 util::Status SpanJoinOperatorTable::Query::Initialize(
     const QueryConstraints& qc,
-    sqlite3_value** argv) {
+    sqlite3_value** argv,
+    InitialEofBehavior eof_behavior) {
   *this = Query(table_, definition(), db_);
   sql_query_ = CreateSqlQuery(
       table_->ComputeSqlConstraintsForDefinition(*defn_, qc, argv));
-  return Rewind();
+  util::Status status = Rewind();
+  if (!status.ok())
+    return status;
+  if (eof_behavior == InitialEofBehavior::kTreatAsMissingPartitionShadow &&
+      IsEof()) {
+    state_ = State::kMissingPartitionShadow;
+  }
+  return status;
 }
 
 util::Status SpanJoinOperatorTable::Query::Next() {
diff --git a/src/trace_processor/sqlite/span_join_operator_table.h b/src/trace_processor/sqlite/span_join_operator_table.h
index 4aa3522..ebfb867 100644
--- a/src/trace_processor/sqlite/span_join_operator_table.h
+++ b/src/trace_processor/sqlite/span_join_operator_table.h
@@ -165,8 +165,16 @@
     Query(Query&&) noexcept = default;
     Query& operator=(Query&&) = default;
 
+    enum class InitialEofBehavior {
+      kTreatAsEof,
+      kTreatAsMissingPartitionShadow
+    };
+
     // Initializes the query with the given constraints and query parameters.
-    util::Status Initialize(const QueryConstraints& qc, sqlite3_value** argv);
+    util::Status Initialize(
+        const QueryConstraints& qc,
+        sqlite3_value** argv,
+        InitialEofBehavior eof_behavior = InitialEofBehavior::kTreatAsEof);
 
     // Forwards the query to the next valid slice.
     util::Status Next();
diff --git a/src/trace_processor/sqlite/span_join_operator_table_unittest.cc b/src/trace_processor/sqlite/span_join_operator_table_unittest.cc
index 87bb645..4c28397 100644
--- a/src/trace_processor/sqlite/span_join_operator_table_unittest.cc
+++ b/src/trace_processor/sqlite/span_join_operator_table_unittest.cc
@@ -254,6 +254,92 @@
   ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_DONE);
 }
 
+TEST_F(SpanJoinOperatorTableTest, LeftJoinTwoSpanTables) {
+  RunStatement(
+      "CREATE TEMP TABLE f("
+      "ts BIG INT PRIMARY KEY, "
+      "dur BIG INT, "
+      "cpu UNSIGNED INT"
+      ");");
+  RunStatement(
+      "CREATE TEMP TABLE s("
+      "ts BIG INT PRIMARY KEY, "
+      "dur BIG INT, "
+      "tid UNSIGNED INT"
+      ");");
+  RunStatement("CREATE VIRTUAL TABLE sp USING span_left_join(f, s);");
+
+  RunStatement("INSERT INTO f VALUES(100, 10, 0);");
+  RunStatement("INSERT INTO f VALUES(110, 50, 1);");
+
+  RunStatement("INSERT INTO s VALUES(100, 5, 1);");
+  RunStatement("INSERT INTO s VALUES(110, 40, 2);");
+  RunStatement("INSERT INTO s VALUES(150, 50, 3);");
+
+  PrepareValidStatement("SELECT * FROM sp");
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 100);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 5);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 0);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 3), 1);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 105);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 5);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 0);
+  ASSERT_EQ(sqlite3_column_type(stmt_.get(), 3), SQLITE_NULL);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 110);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 40);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 1);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 3), 2);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 150);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 10);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 1);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 3), 3);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_DONE);
+}
+
+TEST_F(SpanJoinOperatorTableTest, LeftJoinTwoSpanTables_EmptyRight) {
+  RunStatement(
+      "CREATE TEMP TABLE f("
+      "ts BIG INT PRIMARY KEY, "
+      "dur BIG INT, "
+      "cpu UNSIGNED INT"
+      ");");
+  RunStatement(
+      "CREATE TEMP TABLE s("
+      "ts BIG INT PRIMARY KEY, "
+      "dur BIG INT, "
+      "tid UNSIGNED INT"
+      ");");
+  RunStatement("CREATE VIRTUAL TABLE sp USING span_left_join(f, s);");
+
+  RunStatement("INSERT INTO f VALUES(100, 10, 0);");
+  RunStatement("INSERT INTO f VALUES(110, 50, 1);");
+
+  PrepareValidStatement("SELECT * FROM sp");
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 100);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 10);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 0);
+  ASSERT_EQ(sqlite3_column_type(stmt_.get(), 3), SQLITE_NULL);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_ROW);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 0), 110);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 1), 50);
+  ASSERT_EQ(sqlite3_column_int64(stmt_.get(), 2), 1);
+  ASSERT_EQ(sqlite3_column_type(stmt_.get(), 3), SQLITE_NULL);
+
+  ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_DONE);
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.cc b/src/trace_processor/sqlite/sqlite_raw_table.cc
index a2ede4c..3f63812 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.cc
+++ b/src/trace_processor/sqlite/sqlite_raw_table.cc
@@ -30,9 +30,11 @@
 
 #include "protos/perfetto/trace/ftrace/binder.pbzero.h"
 #include "protos/perfetto/trace/ftrace/clk.pbzero.h"
+#include "protos/perfetto/trace/ftrace/dpu.pbzero.h"
 #include "protos/perfetto/trace/ftrace/filemap.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/g2d.pbzero.h"
 #include "protos/perfetto/trace/ftrace/irq.pbzero.h"
 #include "protos/perfetto/trace/ftrace/power.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
@@ -63,12 +65,19 @@
 
  private:
   using ValueWriter = std::function<void(const Variadic&)>;
+  using SerializerValueWriter = void (ArgsSerializer::*)(const Variadic&);
 
   void WriteArgForField(uint32_t field_id) {
     WriteArgForField(field_id,
                      [this](const Variadic& v) { return WriteValue(v); });
   }
 
+  void WriteArgForField(uint32_t field_id, SerializerValueWriter writer) {
+    WriteArgForField(field_id, [this, writer](const Variadic& variadic) {
+      (this->*writer)(variadic);
+    });
+  }
+
   void WriteArgForField(uint32_t field_id, ValueWriter writer) {
     WriteArgAtRow(FieldIdToRow(field_id), writer);
   }
@@ -78,6 +87,12 @@
                        [this](const Variadic& v) { return WriteValue(v); });
   }
 
+  void WriteValueForField(uint32_t field_id, SerializerValueWriter writer) {
+    WriteValueForField(field_id, [this, writer](const Variadic& variadic) {
+      (this->*writer)(variadic);
+    });
+  }
+
   void WriteValueForField(uint32_t field_id, ValueWriter writer) {
     writer(storage_->GetArgValue(FieldIdToRow(field_id)));
   }
@@ -89,6 +104,16 @@
 
   void WriteArgAtRow(uint32_t arg_index, ValueWriter writer);
 
+  void WriteKernelFnValue(const Variadic& value) {
+    if (value.type == Variadic::Type::kUint) {
+      writer_->AppendHexInt(value.uint_value);
+    } else if (value.type == Variadic::Type::kString) {
+      WriteValue(value);
+    } else {
+      PERFETTO_DFATAL("Invalid field type %d", static_cast<int>(value.type));
+    }
+  }
+
   void WriteValue(const Variadic& variadic);
 
   uint32_t FieldIdToRow(uint32_t field_id) {
@@ -322,10 +347,8 @@
     using SBR = protos::pbzero::SchedBlockedReasonFtraceEvent;
     WriteArgForField(SBR::kPidFieldNumber);
     WriteArgForField(SBR::kIoWaitFieldNumber);
-    WriteArgForField(SBR::kCallerFieldNumber, [this](const Variadic& value) {
-      PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
-      writer_->AppendHexInt(value.uint_value);
-    });
+    WriteArgForField(SBR::kCallerFieldNumber,
+                     &ArgsSerializer::WriteKernelFnValue);
     return;
   } else if (event_name_ == "workqueue_activate_work") {
     using WAW = protos::pbzero::WorkqueueActivateWorkFtraceEvent;
@@ -344,10 +367,7 @@
     });
     writer_->AppendString(": function ");
     WriteValueForField(WES::kFunctionFieldNumber,
-                       [this](const Variadic& value) {
-                         PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
-                         writer_->AppendHexInt(value.uint_value);
-                       });
+                       &ArgsSerializer::WriteKernelFnValue);
     return;
   } else if (event_name_ == "workqueue_execute_end") {
     using WE = protos::pbzero::WorkqueueExecuteEndFtraceEvent;
@@ -364,10 +384,8 @@
       PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
       writer_->AppendHexInt(value.uint_value);
     });
-    WriteArgForField(WQW::kFunctionFieldNumber, [this](const Variadic& value) {
-      PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
-      writer_->AppendHexInt(value.uint_value);
-    });
+    WriteArgForField(WQW::kFunctionFieldNumber,
+                     &ArgsSerializer::WriteKernelFnValue);
     WriteArgForField(WQW::kWorkqueueFieldNumber, [this](const Variadic& value) {
       PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
       writer_->AppendHexInt(value.uint_value);
@@ -409,6 +427,32 @@
     });
     writer_->AppendString("]");
     return;
+  } else if (event_name_ == "dpu_tracing_mark_write") {
+    using TMW = protos::pbzero::DpuTracingMarkWriteFtraceEvent;
+    WriteValueForField(TMW::kTypeFieldNumber, [this](const Variadic& value) {
+      PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
+      writer_->AppendChar(static_cast<char>(value.uint_value));
+    });
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kPidFieldNumber);
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kNameFieldNumber);
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kValueFieldNumber);
+    return;
+  } else if (event_name_ == "g2d_tracing_mark_write") {
+    using TMW = protos::pbzero::G2dTracingMarkWriteFtraceEvent;
+    WriteValueForField(TMW::kTypeFieldNumber, [this](const Variadic& value) {
+      PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
+      writer_->AppendChar(static_cast<char>(value.uint_value));
+    });
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kPidFieldNumber);
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kNameFieldNumber);
+    writer_->AppendString("|");
+    WriteValueForField(TMW::kValueFieldNumber);
+    return;
   }
   for (auto it = row_map_.IterateRows(); it; it.Next()) {
     WriteArgAtRow(it.row());
@@ -519,7 +563,8 @@
   StringId event_name_id = raw.name()[raw_row];
   NullTermStringView event_name = storage_->GetString(event_name_id);
   writer.AppendChar(' ');
-  if (event_name == "print") {
+  if (event_name == "print" || event_name == "g2d_tracing_mark_write" ||
+      event_name == "dpu_tracing_mark_write") {
     writer.AppendString("tracing_mark_write");
   } else {
     writer.AppendString(event_name.c_str(), event_name.size());
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.h b/src/trace_processor/sqlite/sqlite_raw_table.h
index 2e6d774..3722d9b 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.h
+++ b/src/trace_processor/sqlite/sqlite_raw_table.h
@@ -53,7 +53,7 @@
   };
 
   SqliteRawTable(sqlite3*, Context);
-  virtual ~SqliteRawTable();
+  ~SqliteRawTable() override;
 
   static void RegisterTable(sqlite3* db, QueryCache*, TraceProcessorContext*);
 
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
index c3229d3..2540a75 100644
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ b/src/trace_processor/sqlite/sqlite_table.cc
@@ -32,7 +32,7 @@
 std::string TypeToString(SqlValue::Type type) {
   switch (type) {
     case SqlValue::Type::kString:
-      return "STRING";
+      return "TEXT";
     case SqlValue::Type::kLong:
       return "BIG INT";
     case SqlValue::Type::kDouble:
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 4896df7..aa7a0fd 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -26,6 +26,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
@@ -380,9 +381,11 @@
   sqlite3_result_double(ctx, value);
 }
 
-inline std::vector<SqliteTable::Column> GetColumnsForTable(
+inline util::Status GetColumnsForTable(
     sqlite3* db,
-    const std::string& raw_table_name) {
+    const std::string& raw_table_name,
+    std::vector<SqliteTable::Column>& columns) {
+  PERFETTO_DCHECK(columns.empty());
   char sql[1024];
   const char kRawSql[] = "SELECT name, type from pragma_table_info(\"%s\")";
 
@@ -394,21 +397,18 @@
   sqlite3_stmt* raw_stmt = nullptr;
   int err = sqlite3_prepare_v2(db, sql, n, &raw_stmt, nullptr);
   if (err != SQLITE_OK) {
-    PERFETTO_ELOG("Preparing database failed");
-    return {};
+    return util::ErrStatus("Preparing database failed");
   }
   ScopedStmt stmt(raw_stmt);
   PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
 
-  std::vector<SqliteTable::Column> columns;
   for (;;) {
     err = sqlite3_step(raw_stmt);
     if (err == SQLITE_DONE)
       break;
     if (err != SQLITE_ROW) {
-      PERFETTO_ELOG("Querying schema of table %s failed",
-                    raw_table_name.c_str());
-      return {};
+      return util::ErrStatus("Querying schema of table %s failed",
+                             raw_table_name.c_str());
     }
 
     const char* name =
@@ -416,31 +416,32 @@
     const char* raw_type =
         reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 1));
     if (!name || !raw_type || !*name) {
-      PERFETTO_FATAL("Schema for %s has invalid column values",
-                     raw_table_name.c_str());
+      return util::ErrStatus("Schema for %s has invalid column values",
+                             raw_table_name.c_str());
     }
 
     SqlValue::Type type;
-    if (strcmp(raw_type, "STRING") == 0) {
+    if (base::CaseInsensitiveEqual(raw_type, "STRING") ||
+        base::CaseInsensitiveEqual(raw_type, "TEXT")) {
       type = SqlValue::Type::kString;
-    } else if (strcmp(raw_type, "DOUBLE") == 0) {
+    } else if (base::CaseInsensitiveEqual(raw_type, "DOUBLE")) {
       type = SqlValue::Type::kDouble;
-    } else if (strcmp(raw_type, "BIG INT") == 0 ||
-               strcmp(raw_type, "UNSIGNED INT") == 0 ||
-               strcmp(raw_type, "INT") == 0 ||
-               strcmp(raw_type, "BOOLEAN") == 0) {
+    } else if (base::CaseInsensitiveEqual(raw_type, "BIG INT") ||
+               base::CaseInsensitiveEqual(raw_type, "UNSIGNED INT") ||
+               base::CaseInsensitiveEqual(raw_type, "INT") ||
+               base::CaseInsensitiveEqual(raw_type, "BOOLEAN")) {
       type = SqlValue::Type::kLong;
     } else if (!*raw_type) {
       PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
                     name);
       type = SqlValue::Type::kNull;
     } else {
-      PERFETTO_FATAL("Unknown column type '%s' on table %s", raw_type,
-                     raw_table_name.c_str());
+      return util::ErrStatus("Unknown column type '%s' on table %s", raw_type,
+                             raw_table_name.c_str());
     }
     columns.emplace_back(columns.size(), name, type);
   }
-  return columns;
+  return util::OkStatus();
 }
 
 template <typename T>
diff --git a/src/trace_processor/sqlite/sqlite_utils_unittest.cc b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
new file mode 100644
index 0000000..1d1de13
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+class GetColumnsForTableTest : public ::testing::Test {
+ public:
+  GetColumnsForTableTest() {
+    sqlite3* db = nullptr;
+    PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK);
+    PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
+    db_.reset(db);
+  }
+
+  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);
+    stmt_.reset(stmt);
+  }
+
+  void RunStatement(const std::string& sql) {
+    PrepareValidStatement(sql);
+    ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_DONE);
+  }
+
+ protected:
+  ScopedDb db_;
+  ScopedStmt stmt_;
+};
+
+TEST_F(GetColumnsForTableTest, ValidInput) {
+  RunStatement("CREATE TABLE foo (name STRING, ts INT, dur INT);");
+  std::vector<SqliteTable::Column> columns;
+  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  ASSERT_TRUE(status.ok());
+}
+
+TEST_F(GetColumnsForTableTest, UnknownType) {
+  // Currently GetColumnsForTable does not work with tables containing types it
+  // doesn't recognise. This just ensures that the query fails rather than
+  // crashing.
+  RunStatement("CREATE TABLE foo (name NUM, ts INT, dur INT);");
+  std::vector<SqliteTable::Column> columns;
+  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  ASSERT_FALSE(status.ok());
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/storage/metadata.h b/src/trace_processor/storage/metadata.h
index 4796ae4..adf2bc7 100644
--- a/src/trace_processor/storage/metadata.h
+++ b/src/trace_processor/storage/metadata.h
@@ -49,7 +49,8 @@
   F(trace_size_bytes,                  KeyType::kSingle,  Variadic::kInt),    \
   F(all_data_source_started_ns,        KeyType::kSingle,  Variadic::kInt),    \
   F(tracing_started_ns,                KeyType::kSingle,  Variadic::kInt),    \
-  F(tracing_disabled_ns,               KeyType::kSingle,  Variadic::kInt)
+  F(tracing_disabled_ns,               KeyType::kSingle,  Variadic::kInt),    \
+  F(trace_config_pbtxt,                KeyType::kSingle, Variadic::kString)
 // clang-format on
 
 // Compile time list of metadata items.
@@ -60,7 +61,9 @@
 // clang-format
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 #define PERFETTO_TP_META_TYPE_ENUM(varname, ...) varname
 enum class KeyType : size_t {
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 4656953..2f69c69 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -27,10 +27,11 @@
 // clang-format off
 #define PERFETTO_TP_STATS(F)                                                   \
   F(android_log_num_failed,             kSingle,  kError,    kTrace,    ""),   \
-  F(android_log_num_skipped,            kSingle,  kError,    kTrace,    ""),   \
+  F(android_log_num_skipped,            kSingle,  kInfo,     kTrace,    ""),   \
   F(android_log_num_total,              kSingle,  kInfo,     kTrace,    ""),   \
   F(counter_events_out_of_order,        kSingle,  kError,    kAnalysis, ""),   \
   F(ftrace_bundle_tokenizer_errors,     kSingle,  kError,    kAnalysis, ""),   \
+  F(deobfuscate_location_parse_error,   kSingle,  kError,    kTrace,    ""),   \
   F(ftrace_cpu_bytes_read_begin,        kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_bytes_read_end,          kIndexed, kInfo,     kTrace,    ""),   \
   F(ftrace_cpu_commit_overrun_begin,    kIndexed, kError,    kTrace,    ""),   \
@@ -71,6 +72,12 @@
   F(rss_stat_unknown_thread_for_mm_id,  kSingle,  kInfo,     kAnalysis, ""),   \
   F(sched_switch_out_of_order,          kSingle,  kError,    kAnalysis, ""),   \
   F(slice_out_of_order,                 kSingle,  kError,    kAnalysis, ""),   \
+  F(flow_duplicate_id,                  kSingle,  kError,    kTrace,    ""),   \
+  F(flow_no_enclosing_slice,            kSingle,  kError,    kTrace,    ""),   \
+  F(flow_step_without_start,            kSingle,  kInfo,     kTrace,    ""),   \
+  F(flow_end_without_start,             kSingle,  kInfo,     kTrace,    ""),   \
+  F(flow_invalid_id,                    kSingle,  kError,    kTrace,    ""),   \
+  F(flow_without_direction,             kSingle,  kError,    kTrace,    ""),   \
   F(stackprofile_invalid_string_id,     kSingle,  kError,    kTrace,    ""),   \
   F(stackprofile_invalid_mapping_id,    kSingle,  kError,    kTrace,    ""),   \
   F(stackprofile_invalid_frame_id,      kSingle,  kError,    kTrace,    ""),   \
@@ -119,7 +126,6 @@
   F(heap_graph_non_finalized_graph,     kSingle,  kError,    kTrace,    ""),   \
   F(heap_graph_malformed_packet,        kIndexed, kError,    kTrace,    ""),   \
   F(heap_graph_missing_packet,          kIndexed, kError,    kTrace,    ""),   \
-  F(heap_graph_location_parse_error,    kSingle,  kError,    kTrace,    ""),   \
   F(heapprofd_buffer_corrupted,         kIndexed, kError,    kTrace,           \
       "Shared memory buffer corrupted. This is a bug or memory corruption "    \
       "in the target. Indexed by target upid."),                               \
@@ -149,6 +155,7 @@
   F(ninja_parse_errors,                 kSingle,  kError,    kTrace,    ""),   \
   F(perf_samples_skipped,               kSingle,  kInfo,     kTrace,    ""),   \
   F(perf_samples_skipped_dataloss,      kSingle,  kDataLoss, kTrace,    ""),   \
+  F(memory_snapshot_parser_failure,     kSingle,  kError,    kAnalysis, ""),   \
   F(thread_time_in_state_out_of_order,  kSingle,  kError,    kAnalysis, ""),   \
   F(thread_time_in_state_unknown_cpu_freq,                                     \
                                         kSingle,  kError,    kAnalysis, ""),   \
@@ -183,7 +190,9 @@
 };
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 // Declares an enum of literals (one for each stat). The enum values of each
 // literal corresponds to the string index in the arrays below.
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 7f2fd2a..6c42178 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -32,11 +32,14 @@
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/tables/android_tables.h"
 #include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/tables/flow_tables.h"
+#include "src/trace_processor/tables/memory_tables.h"
 #include "src/trace_processor/tables/metadata_tables.h"
 #include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/tables/slice_tables.h"
@@ -88,6 +91,10 @@
 
 using VulkanAllocId = tables::VulkanMemoryAllocationsTable::Id;
 
+using ProcessMemorySnapshotId = tables::ProcessMemorySnapshotTable::Id;
+
+using SnapshotNodeId = tables::MemorySnapshotNodeTable::Id;
+
 // TODO(lalitm): this is a temporary hack while migrating the counters table and
 // will be removed when the migration is complete.
 static const TrackId kInvalidTrackId =
@@ -447,6 +454,9 @@
   const tables::SliceTable& slice_table() const { return slice_table_; }
   tables::SliceTable* mutable_slice_table() { return &slice_table_; }
 
+  const tables::FlowTable& flow_table() const { return flow_table_; }
+  tables::FlowTable* mutable_flow_table() { return &flow_table_; }
+
   const ThreadSlices& thread_slices() const { return thread_slices_; }
   ThreadSlices* mutable_thread_slices() { return &thread_slices_; }
 
@@ -613,6 +623,35 @@
     return &graphics_frame_slice_table_;
   }
 
+  const tables::MemorySnapshotTable& memory_snapshot_table() const {
+    return memory_snapshot_table_;
+  }
+  tables::MemorySnapshotTable* mutable_memory_snapshot_table() {
+    return &memory_snapshot_table_;
+  }
+
+  const tables::ProcessMemorySnapshotTable& process_memory_snapshot_table()
+      const {
+    return process_memory_snapshot_table_;
+  }
+  tables::ProcessMemorySnapshotTable* mutable_process_memory_snapshot_table() {
+    return &process_memory_snapshot_table_;
+  }
+
+  const tables::MemorySnapshotNodeTable& memory_snapshot_node_table() const {
+    return memory_snapshot_node_table_;
+  }
+  tables::MemorySnapshotNodeTable* mutable_memory_snapshot_node_table() {
+    return &memory_snapshot_node_table_;
+  }
+
+  const tables::MemorySnapshotEdgeTable& memory_snapshot_edge_table() const {
+    return memory_snapshot_edge_table_;
+  }
+  tables::MemorySnapshotEdgeTable* mutable_memory_snapshot_edge_table() {
+    return &memory_snapshot_edge_table_;
+  }
+
   const StringPool& string_pool() const { return string_pool_; }
   StringPool* mutable_string_pool() { return &string_pool_; }
 
@@ -623,35 +662,23 @@
   // Returns (0, 0) if the trace is empty.
   std::pair<int64_t, int64_t> GetTraceTimestampBoundsNs() const;
 
-  // TODO(lalitm): remove this when we have a better home.
-  std::vector<MappingId> FindMappingRow(StringId name,
-                                        StringId build_id) const {
-    auto it = stack_profile_mapping_index_.find(std::make_pair(name, build_id));
-    if (it == stack_profile_mapping_index_.end())
-      return {};
-    return it->second;
-  }
-
-  // TODO(lalitm): remove this when we have a better home.
-  void InsertMappingId(StringId name, StringId build_id, MappingId row) {
-    auto pair = std::make_pair(name, build_id);
-    stack_profile_mapping_index_[pair].emplace_back(row);
-  }
-
-  // TODO(lalitm): remove this when we have a better home.
-  std::vector<FrameId> FindFrameIds(MappingId mapping_row,
-                                    uint64_t rel_pc) const {
-    auto it =
-        stack_profile_frame_index_.find(std::make_pair(mapping_row, rel_pc));
-    if (it == stack_profile_frame_index_.end())
-      return {};
-    return it->second;
-  }
-
-  // TODO(lalitm): remove this when we have a better home.
-  void InsertFrameRow(MappingId mapping_row, uint64_t rel_pc, FrameId row) {
-    auto pair = std::make_pair(mapping_row, rel_pc);
-    stack_profile_frame_index_[pair].emplace_back(row);
+  util::Status ExtractArg(uint32_t arg_set_id,
+                          const char* key,
+                          base::Optional<Variadic>* result) {
+    const auto& args = arg_table();
+    RowMap filtered = args.FilterToRowMap(
+        {args.arg_set_id().eq(arg_set_id), args.key().eq(key)});
+    if (filtered.empty()) {
+      *result = base::nullopt;
+      return util::OkStatus();
+    }
+    if (filtered.size() > 1) {
+      return util::ErrStatus(
+          "EXTRACT_ARG: received multiple args matching arg set id and key");
+    }
+    uint32_t idx = filtered.Get(0);
+    *result = GetArgValue(idx);
+    return util::OkStatus();
   }
 
   Variadic GetArgValue(uint32_t row) const {
@@ -714,14 +741,6 @@
   TraceStorage(TraceStorage&&) = delete;
   TraceStorage& operator=(TraceStorage&&) = delete;
 
-  // TODO(lalitm): remove this when we find a better home for this.
-  using MappingKey = std::pair<StringId /* name */, StringId /* build id */>;
-  std::map<MappingKey, std::vector<MappingId>> stack_profile_mapping_index_;
-
-  // TODO(lalitm): remove this when we find a better home for this.
-  using FrameKey = std::pair<MappingId, uint64_t /* rel_pc */>;
-  std::map<FrameKey, std::vector<FrameId>> stack_profile_frame_index_;
-
   // One entry for each unique string in the trace.
   StringPool string_pool_;
 
@@ -765,6 +784,9 @@
   // Slices coming from userspace events (e.g. Chromium TRACE_EVENT macros).
   tables::SliceTable slice_table_{&string_pool_, nullptr};
 
+  // Flow events from userspace events (e.g. Chromium TRACE_EVENT macros).
+  tables::FlowTable flow_table_{&string_pool_, nullptr};
+
   // Slices from CPU scheduling data.
   tables::SchedSliceTable sched_slice_table_{&string_pool_, nullptr};
 
@@ -831,6 +853,15 @@
   tables::GraphicsFrameSliceTable graphics_frame_slice_table_{&string_pool_,
                                                               &slice_table_};
 
+  // Metadata for memory snapshot.
+  tables::MemorySnapshotTable memory_snapshot_table_{&string_pool_, nullptr};
+  tables::ProcessMemorySnapshotTable process_memory_snapshot_table_{
+      &string_pool_, nullptr};
+  tables::MemorySnapshotNodeTable memory_snapshot_node_table_{&string_pool_,
+                                                              nullptr};
+  tables::MemorySnapshotEdgeTable memory_snapshot_edge_table_{&string_pool_,
+                                                              nullptr};
+
   // The below array allow us to map between enums and their string
   // representations.
   std::array<StringId, Variadic::kMaxType + 1> variadic_type_ids_;
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 7f6ac2b..a6d7e86 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -18,8 +18,10 @@
   sources = [
     "android_tables.h",
     "counter_tables.h",
+    "flow_tables.h",
     "macros.h",
     "macros_internal.h",
+    "memory_tables.h",
     "metadata_tables.h",
     "profiler_tables.h",
     "slice_tables.h",
diff --git a/src/trace_processor/tables/flow_tables.h b/src/trace_processor/tables/flow_tables.h
new file mode 100644
index 0000000..6eeff36
--- /dev/null
+++ b/src/trace_processor/tables/flow_tables.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
+#define SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
+
+#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/tables/slice_tables.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace tables {
+
+// @param arg_set_id {@joinable args.arg_set_id}
+#define PERFETTO_TP_FLOW_DEF(NAME, PARENT, C) \
+  NAME(FlowTable, "flow")                     \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)           \
+  C(SliceTable::Id, slice_out)                \
+  C(SliceTable::Id, slice_in)                 \
+  C(uint32_t, arg_set_id)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_FLOW_DEF);
+
+}  // namespace tables
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 6710c69..a7b8325 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -140,7 +140,9 @@
 }  // namespace macros_internal
 
 // Ignore GCC warning about a missing argument for a variadic macro parameter.
+#if defined(__GNUC__) || defined(__clang__)
 #pragma GCC system_header
+#endif
 
 // Basic helper macros.
 #define PERFETTO_TP_NOOP(...)
@@ -223,11 +225,15 @@
 
 #define PERFETTO_TP_COLUMN_FLAG_CHOOSER(type, name, maybe_flags, fn, ...) fn
 
-#define PERFETTO_TP_COLUMN_FLAG(...)                                    \
-  PERFETTO_TP_COLUMN_FLAG_CHOOSER(__VA_ARGS__,                          \
-                                  PERFETTO_TP_COLUMN_FLAG_HAS_FLAG_COL, \
-                                  PERFETTO_TP_COLUMN_FLAG_NO_FLAG_COL)  \
-  (__VA_ARGS__)
+// MSVC has slightly different rules about __VA_ARGS__ expansion. This makes it
+// behave similarly to GCC/Clang.
+// See https://stackoverflow.com/q/5134523/14028266 .
+#define PERFETTO_TP_EXPAND_VA_ARGS(x) x
+
+#define PERFETTO_TP_COLUMN_FLAG(...)                          \
+  PERFETTO_TP_EXPAND_VA_ARGS(PERFETTO_TP_COLUMN_FLAG_CHOOSER( \
+      __VA_ARGS__, PERFETTO_TP_COLUMN_FLAG_HAS_FLAG_COL,      \
+      PERFETTO_TP_COLUMN_FLAG_NO_FLAG_COL)(__VA_ARGS__))
 
 // Creates the sparse vector with the given flags.
 #define PERFETTO_TP_TABLE_CONSTRUCTOR_SV(type, name, ...)               \
diff --git a/src/trace_processor/tables/macros_unittest.cc b/src/trace_processor/tables/macros_unittest.cc
index fdc6327..c9dd4b4 100644
--- a/src/trace_processor/tables/macros_unittest.cc
+++ b/src/trace_processor/tables/macros_unittest.cc
@@ -67,9 +67,9 @@
 };
 
 TEST_F(TableMacrosUnittest, Name) {
-  ASSERT_EQ(event_.table_name(), "event");
-  ASSERT_EQ(slice_.table_name(), "slice");
-  ASSERT_EQ(cpu_slice_.table_name(), "cpu_slice");
+  ASSERT_STREQ(event_.table_name(), "event");
+  ASSERT_STREQ(slice_.table_name(), "slice");
+  ASSERT_STREQ(cpu_slice_.table_name(), "cpu_slice");
 }
 
 TEST_F(TableMacrosUnittest, InsertParent) {
diff --git a/src/trace_processor/tables/memory_tables.h b/src/trace_processor/tables/memory_tables.h
new file mode 100644
index 0000000..3758318
--- /dev/null
+++ b/src/trace_processor/tables/memory_tables.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_TABLES_MEMORY_TABLES_H_
+#define SRC_TRACE_PROCESSOR_TABLES_MEMORY_TABLES_H_
+
+#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/tables/track_tables.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace tables {
+
+// @tablegroup Memory Snapshots
+#define PERFETTO_TP_MEMORY_SNAPSHOT_DEF(NAME, PARENT, C) \
+  NAME(MemorySnapshotTable, "memory_snapshot")           \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                      \
+  C(int64_t, timestamp)                                  \
+  C(TrackTable::Id, track_id)                            \
+  C(StringPool::Id, detail_level)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_MEMORY_SNAPSHOT_DEF);
+
+// @tablegroup Memory Snapshots
+#define PERFETTO_TP_PROCESS_MEMORY_SNAPSHOT_DEF(NAME, PARENT, C) \
+  NAME(ProcessMemorySnapshotTable, "process_memory_snapshot")    \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                              \
+  C(MemorySnapshotTable::Id, snapshot_id)                        \
+  C(uint32_t, upid)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_MEMORY_SNAPSHOT_DEF);
+
+// @tablegroup Memory Snapshots
+#define PERFETTO_TP_MEMORY_SNAPSHOT_NODE_DEF(NAME, PARENT, C)    \
+  NAME(MemorySnapshotNodeTable, "memory_snapshot_node")          \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                              \
+  C(ProcessMemorySnapshotTable::Id, process_snapshot_id)         \
+  C(base::Optional<MemorySnapshotNodeTable::Id>, parent_node_id) \
+  C(StringPool::Id, path)                                        \
+  C(int64_t, size)                                               \
+  C(int64_t, effective_size)                                     \
+  C(base::Optional<uint32_t>, arg_set_id)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_MEMORY_SNAPSHOT_NODE_DEF);
+
+// @tablegroup Memory Snapshots
+#define PERFETTO_TP_MEMORY_SNAPSHOT_EDGE_DEF(NAME, PARENT, C) \
+  NAME(MemorySnapshotEdgeTable, "memory_snapshot_edge")       \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                           \
+  C(MemorySnapshotNodeTable::Id, source_node_id)              \
+  C(MemorySnapshotNodeTable::Id, target_node_id)              \
+  C(uint32_t, importance)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_MEMORY_SNAPSHOT_EDGE_DEF);
+
+}  // namespace tables
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_TABLES_MEMORY_TABLES_H_
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
index 344a4eb..afff771 100644
--- a/src/trace_processor/tables/profiler_tables.h
+++ b/src/trace_processor/tables/profiler_tables.h
@@ -33,6 +33,19 @@
 // @param size_kb Total size of the mapping.
 // @param private_dirty_kb KB of this mapping that are private dirty  RSS.
 // @param swap_kb KB of this mapping that are in swap.
+// @param file_name
+// @param file_name_iid
+// @param path_iid
+// @param start_address
+// @param module_timestamp
+// @param module_debugid
+// @param module_debug_path
+// @param protection_flags
+// @param private_clean_resident_kb
+// @param shared_dirty_resident_kb
+// @param shared_clean_resident_kb
+// @param locked_kb
+// @param proportional_resident_kb
 // @tablegroup Callstack profilers
 #define PERFETTO_TP_PROFILER_SMAPS_DEF(NAME, PARENT, C) \
   NAME(ProfilerSmapsTable, "profiler_smaps")            \
@@ -42,7 +55,18 @@
   C(StringPool::Id, path)                               \
   C(int64_t, size_kb)                                   \
   C(int64_t, private_dirty_kb)                          \
-  C(int64_t, swap_kb)
+  C(int64_t, swap_kb)                                   \
+  C(StringPool::Id, file_name)                          \
+  C(int64_t, start_address)                             \
+  C(int64_t, module_timestamp)                          \
+  C(StringPool::Id, module_debugid)                     \
+  C(StringPool::Id, module_debug_path)                  \
+  C(int64_t, protection_flags)                          \
+  C(int64_t, private_clean_resident_kb)                 \
+  C(int64_t, shared_dirty_resident_kb)                  \
+  C(int64_t, shared_clean_resident_kb)                  \
+  C(int64_t, locked_kb)                                 \
+  C(int64_t, proportional_resident_kb)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_PROFILER_SMAPS_DEF);
 
@@ -99,7 +123,8 @@
   C(StringPool::Id, name)                                    \
   C(StackProfileMappingTable::Id, mapping)                   \
   C(int64_t, rel_pc)                                         \
-  C(base::Optional<uint32_t>, symbol_set_id)
+  C(base::Optional<uint32_t>, symbol_set_id)                 \
+  C(base::Optional<StringPool::Id>, deobfuscated_name)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_STACK_PROFILE_FRAME_DEF);
 
@@ -257,13 +282,18 @@
 // for it provided, the deobfuscated name.
 // @param location the APK / Dex / JAR file the class is contained in.
 // @tablegroup ART Heap Profiler
-#define PERFETTO_TP_HEAP_GRAPH_CLASS_DEF(NAME, PARENT, C) \
-  NAME(HeapGraphClassTable, "heap_graph_class")           \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                       \
-  C(StringPool::Id, name)                                 \
-  C(base::Optional<StringPool::Id>, deobfuscated_name)    \
-  C(base::Optional<StringPool::Id>, location)             \
-  C(base::Optional<HeapGraphClassTable::Id>, superclass_id)
+//
+// classloader_id should really be HeapGraphObject::id, but that would
+// create a loop, which is currently not possible.
+// TODO(lalitm): resolve this
+#define PERFETTO_TP_HEAP_GRAPH_CLASS_DEF(NAME, PARENT, C)   \
+  NAME(HeapGraphClassTable, "heap_graph_class")             \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                         \
+  C(StringPool::Id, name)                                   \
+  C(base::Optional<StringPool::Id>, deobfuscated_name)      \
+  C(base::Optional<StringPool::Id>, location)               \
+  C(base::Optional<HeapGraphClassTable::Id>, superclass_id) \
+  C(base::Optional<uint32_t>, classloader_id)
 
 PERFETTO_TP_TABLE(PERFETTO_TP_HEAP_GRAPH_CLASS_DEF);
 
diff --git a/src/trace_processor/tables/slice_tables.h b/src/trace_processor/tables/slice_tables.h
index 2ba3a0b..a4bb330 100644
--- a/src/trace_processor/tables/slice_tables.h
+++ b/src/trace_processor/tables/slice_tables.h
@@ -71,6 +71,20 @@
 PERFETTO_TP_TABLE(PERFETTO_TP_SCHED_SLICE_TABLE_DEF);
 
 // @tablegroup Events
+// @param utid {@joinable thread.utid}
+#define PERFETTO_TP_THREAD_STATE_TABLE_DEF(NAME, PARENT, C) \
+  NAME(ThreadStateTable, "thread_state")                    \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                         \
+  C(int64_t, ts)                                            \
+  C(int64_t, dur)                                           \
+  C(base::Optional<uint32_t>, cpu)                          \
+  C(uint32_t, utid)                                         \
+  C(StringPool::Id, state)                                  \
+  C(base::Optional<uint32_t>, io_wait)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_THREAD_STATE_TABLE_DEF);
+
+// @tablegroup Events
 #define PERFETTO_TP_GPU_SLICES_DEF(NAME, PARENT, C) \
   NAME(GpuSliceTable, "gpu_slice")                  \
   PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)            \
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index d33499e..1bc54b1 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -16,6 +16,8 @@
 
 #include "src/trace_processor/tables/android_tables.h"
 #include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/tables/flow_tables.h"
+#include "src/trace_processor/tables/memory_tables.h"
 #include "src/trace_processor/tables/metadata_tables.h"
 #include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/tables/slice_tables.h"
@@ -64,11 +66,13 @@
 
 // slice_tables.h
 SliceTable::~SliceTable() = default;
+FlowTable::~FlowTable() = default;
 InstantTable::~InstantTable() = default;
 SchedSliceTable::~SchedSliceTable() = default;
 GpuSliceTable::~GpuSliceTable() = default;
 GraphicsFrameSliceTable::~GraphicsFrameSliceTable() = default;
 DescribeSliceTable::~DescribeSliceTable() = default;
+ThreadStateTable::~ThreadStateTable() = default;
 
 // track_tables.h
 TrackTable::~TrackTable() = default;
@@ -82,6 +86,13 @@
 IrqCounterTrackTable::~IrqCounterTrackTable() = default;
 SoftirqCounterTrackTable::~SoftirqCounterTrackTable() = default;
 GpuCounterTrackTable::~GpuCounterTrackTable() = default;
+
+// memory_tables.h
+MemorySnapshotTable::~MemorySnapshotTable() = default;
+ProcessMemorySnapshotTable::~ProcessMemorySnapshotTable() = default;
+MemorySnapshotNodeTable::~MemorySnapshotNodeTable() = default;
+MemorySnapshotEdgeTable::~MemorySnapshotEdgeTable() = default;
+
 }  // namespace tables
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/timestamped_trace_piece.h b/src/trace_processor/timestamped_trace_piece.h
index 74b484f..90a83eb 100644
--- a/src/trace_processor/timestamped_trace_piece.h
+++ b/src/trace_processor/timestamped_trace_piece.h
@@ -55,13 +55,18 @@
 
 struct TracePacketData {
   TraceBlobView packet;
+  std::shared_ptr<PacketSequenceStateGeneration> sequence_state;
+};
 
-  PacketSequenceStateGeneration* sequence_state;
+struct FtraceEventData {
+  TraceBlobView event;
+  std::shared_ptr<PacketSequenceStateGeneration> sequence_state;
 };
 
 struct TrackEventData : public TracePacketData {
-  TrackEventData(TraceBlobView pv, PacketSequenceStateGeneration* generation)
-      : TracePacketData{std::move(pv), generation} {}
+  TrackEventData(TraceBlobView pv,
+                 std::shared_ptr<PacketSequenceStateGeneration> generation)
+      : TracePacketData{std::move(pv), std::move(generation)} {}
 
   static constexpr size_t kMaxNumExtraCounters = 8;
 
@@ -86,17 +91,18 @@
     kSystraceLine,
   };
 
-  TimestampedTracePiece(int64_t ts,
-                        uint64_t idx,
-                        TraceBlobView tbv,
-                        PacketSequenceStateGeneration* sequence_state)
-      : packet_data{std::move(tbv), sequence_state},
+  TimestampedTracePiece(
+      int64_t ts,
+      uint64_t idx,
+      TraceBlobView tbv,
+      std::shared_ptr<PacketSequenceStateGeneration> sequence_state)
+      : packet_data{std::move(tbv), std::move(sequence_state)},
         timestamp(ts),
         packet_idx(idx),
         type(Type::kTracePacket) {}
 
-  TimestampedTracePiece(int64_t ts, uint64_t idx, TraceBlobView tbv)
-      : ftrace_event(std::move(tbv)),
+  TimestampedTracePiece(int64_t ts, uint64_t idx, FtraceEventData fed)
+      : ftrace_event(std::move(fed)),
         timestamp(ts),
         packet_idx(idx),
         type(Type::kFtraceEvent) {}
@@ -153,7 +159,7 @@
       case Type::kInvalid:
         break;
       case Type::kFtraceEvent:
-        new (&ftrace_event) TraceBlobView(std::move(ttp.ftrace_event));
+        new (&ftrace_event) FtraceEventData(std::move(ttp.ftrace_event));
         break;
       case Type::kTracePacket:
         new (&packet_data) TracePacketData(std::move(ttp.packet_data));
@@ -208,7 +214,7 @@
       case Type::kInlineSchedWaking:
         break;
       case Type::kFtraceEvent:
-        ftrace_event.~TraceBlobView();
+        ftrace_event.~FtraceEventData();
         break;
       case Type::kTracePacket:
         packet_data.~TracePacketData();
@@ -243,7 +249,7 @@
 
   // Data for different types of TimestampedTracePiece.
   union {
-    TraceBlobView ftrace_event;
+    FtraceEventData ftrace_event;
     TracePacketData packet_data;
     InlineSchedSwitch sched_switch;
     InlineSchedWaking sched_waking;
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 728ce28..e963b54 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -20,8 +20,12 @@
 #include <string>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/trace_processor/trace_processor.h"
+#include "protos/perfetto/common/descriptor.pbzero.h"
+#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
+
 #include "src/base/test/utils.h"
 #include "test/gtest_and_gmock.h"
 
@@ -59,6 +63,8 @@
     return processor_->ExecuteQuery(query.c_str());
   }
 
+  TraceProcessor* Processor() { return processor_.get(); }
+
   size_t RestoreInitialTables() { return processor_->RestoreInitialTables(); }
 
  private:
@@ -170,6 +176,38 @@
   ASSERT_FALSE(it.Next());
 }
 
+TEST_F(TraceProcessorIntegrationTest, SerializeMetricDescriptors) {
+  std::vector<uint8_t> desc_set_bytes = Processor()->GetMetricDescriptors();
+  protos::pbzero::DescriptorSet::Decoder desc_set(desc_set_bytes.data(),
+                                                  desc_set_bytes.size());
+
+  ASSERT_TRUE(desc_set.has_descriptors());
+  int trace_metrics_count = 0;
+  for (auto desc = desc_set.descriptors(); desc; ++desc) {
+    protos::pbzero::DescriptorProto::Decoder proto_desc(*desc);
+    if (proto_desc.name().ToStdString() == ".perfetto.protos.TraceMetrics") {
+      ASSERT_TRUE(proto_desc.has_field());
+      trace_metrics_count++;
+    }
+  }
+
+  // There should be exactly one definition of TraceMetrics. This can be not
+  // true if we're not deduping descriptors properly.
+  ASSERT_EQ(trace_metrics_count, 1);
+}
+
+TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormatted) {
+  std::string metric_output;
+  util::Status status = Processor()->ComputeMetricText(
+      std::vector<std::string>{"test_chrome_metric"},
+      TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
+  ASSERT_TRUE(status.ok());
+  ASSERT_EQ(metric_output,
+            "test_chrome_metric: {\n"
+            "  test_value: 1\n"
+            "}");
+}
+
 // TODO(hjd): Add trace to test_data.
 TEST_F(TraceProcessorIntegrationTest, DISABLED_AndroidBuildTrace) {
   ASSERT_TRUE(LoadTrace("android_build_trace.json", strlen("[\n{")).ok());
@@ -227,12 +265,16 @@
 #define MAYBE_Clusterfuzz21178 DISABLED_Clusterfuzz21178
 #define MAYBE_Clusterfuzz21890 DISABLED_Clusterfuzz21890
 #define MAYBE_Clusterfuzz23053 DISABLED_Clusterfuzz23053
+#define MAYBE_Clusterfuzz28338 DISABLED_Clusterfuzz28338
+#define MAYBE_Clusterfuzz28766 DISABLED_Clusterfuzz28766
 #else  // PERFETTO_DCHECK_IS_ON()
 #define MAYBE_Clusterfuzz20215 Clusterfuzz20215
 #define MAYBE_Clusterfuzz20292 Clusterfuzz20292
 #define MAYBE_Clusterfuzz21178 Clusterfuzz21178
 #define MAYBE_Clusterfuzz21890 Clusterfuzz21890
 #define MAYBE_Clusterfuzz23053 Clusterfuzz23053
+#define MAYBE_Clusterfuzz28338 Clusterfuzz28338
+#define MAYBE_Clusterfuzz28766 Clusterfuzz28766
 #endif  // PERFETTO_DCHECK_IS_ON()
 
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz20215) {
@@ -240,7 +282,7 @@
 }
 
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz20292) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_20292", 4096).ok());
+  ASSERT_FALSE(LoadTrace("clusterfuzz_20292", 4096).ok());
 }
 
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz21178) {
@@ -248,11 +290,19 @@
 }
 
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz21890) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_21890", 4096).ok());
+  ASSERT_FALSE(LoadTrace("clusterfuzz_21890", 4096).ok());
 }
 
 TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz23053) {
-  ASSERT_TRUE(LoadTrace("clusterfuzz_23053", 4096).ok());
+  ASSERT_FALSE(LoadTrace("clusterfuzz_23053", 4096).ok());
+}
+
+TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz28338) {
+  ASSERT_TRUE(LoadTrace("clusterfuzz_28338", 4096).ok());
+}
+
+TEST_F(TraceProcessorIntegrationTest, MAYBE_Clusterfuzz28766) {
+  ASSERT_TRUE(LoadTrace("clusterfuzz_28766", 4096).ok());
 }
 
 TEST_F(TraceProcessorIntegrationTest, RestoreInitialTables) {
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index 961a77f..e2df18f 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -21,11 +21,13 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
 #include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 7e7518f..1fc79f6 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -23,12 +23,15 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "src/trace_processor/dynamic/ancestor_slice_generator.h"
+#include "src/trace_processor/dynamic/ancestor_generator.h"
+#include "src/trace_processor/dynamic/connected_flow_generator.h"
 #include "src/trace_processor/dynamic/descendant_slice_generator.h"
 #include "src/trace_processor/dynamic/describe_slice_generator.h"
 #include "src/trace_processor/dynamic/experimental_counter_dur_generator.h"
 #include "src/trace_processor/dynamic/experimental_flamegraph_generator.h"
+#include "src/trace_processor/dynamic/experimental_sched_upid_generator.h"
 #include "src/trace_processor/dynamic/experimental_slice_layout_generator.h"
+#include "src/trace_processor/dynamic/thread_state_generator.h"
 #include "src/trace_processor/export_json.h"
 #include "src/trace_processor/importers/additional_modules.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
@@ -50,6 +53,7 @@
 #include "src/trace_processor/sqlite/window_operator_table.h"
 #include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/protozero_to_text.h"
 
 #include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
@@ -134,9 +138,28 @@
     PERFETTO_ELOG("Error initializing: %s", error);
     sqlite3_free(error);
   }
+  // Ensure that the entries in power_profile are unique to prevent duplicates
+  // when the power_profile is augmented with additional profiles.
   sqlite3_exec(db,
-               "CREATE TABLE power_profile"
-               "(device STRING, cpu INT, cluster INT, freq INT, power DOUBLE);",
+               "CREATE TABLE power_profile("
+               "device STRING, cpu INT, cluster INT, freq INT, power DOUBLE,"
+               "UNIQUE(device, cpu, cluster, freq));",
+               0, 0, &error);
+  if (error) {
+    PERFETTO_ELOG("Error initializing: %s", error);
+    sqlite3_free(error);
+  }
+  sqlite3_exec(db, "CREATE TABLE trace_metrics(name STRING)", 0, 0, &error);
+  if (error) {
+    PERFETTO_ELOG("Error initializing: %s", error);
+    sqlite3_free(error);
+  }
+  // This is a table intended to be used for metric debugging/developing. Data
+  // in the table is shown specially in the UI, and users can insert rows into
+  // this table to draw more things.
+  sqlite3_exec(db,
+               "CREATE TABLE debug_slices (id BIG INT, name STRING, ts BIG INT,"
+               "dur BIG INT, depth BIG INT)",
                0, 0, &error);
   if (error) {
     PERFETTO_ELOG("Error initializing: %s", error);
@@ -532,35 +555,41 @@
   uint32_t arg_set_id = static_cast<uint32_t>(sqlite3_value_int(argv[0]));
   const char* key = reinterpret_cast<const char*>(sqlite3_value_text(argv[1]));
 
-  const auto& args = storage->arg_table();
-  RowMap filtered = args.FilterToRowMap(
-      {args.arg_set_id().eq(arg_set_id), args.key().eq(key)});
-  if (filtered.size() == 0) {
+  base::Optional<Variadic> opt_value;
+  util::Status status = storage->ExtractArg(arg_set_id, key, &opt_value);
+  if (!status.ok()) {
+    sqlite3_result_error(ctx, status.c_message(), -1);
+    return;
+  }
+
+  if (!opt_value) {
     sqlite3_result_null(ctx);
     return;
   }
-  if (filtered.size() > 1) {
-    sqlite3_result_error(
-        ctx, "EXTRACT_ARG: received multiple args matching arg set id and key",
-        -1);
-  }
 
-  uint32_t idx = filtered.Get(0);
-  Variadic::Type type = *storage->GetVariadicTypeForId(args.value_type()[idx]);
-  switch (type) {
-    case Variadic::kBool:
+  switch (opt_value->type) {
     case Variadic::kInt:
+      sqlite3_result_int64(ctx, opt_value->int_value);
+      break;
+    case Variadic::kBool:
+      sqlite3_result_int64(ctx, opt_value->bool_value);
+      break;
     case Variadic::kUint:
+      sqlite3_result_int64(ctx, static_cast<int64_t>(opt_value->uint_value));
+      break;
     case Variadic::kPointer:
-      sqlite3_result_int64(ctx, *args.int_value()[idx]);
+      sqlite3_result_int64(ctx, static_cast<int64_t>(opt_value->pointer_value));
       break;
     case Variadic::kJson:
+      sqlite3_result_text(ctx, storage->GetString(opt_value->json_value).data(),
+                          -1, nullptr);
+      break;
     case Variadic::kString:
-      sqlite3_result_text(ctx, args.string_value().GetString(idx).data(), -1,
-                          nullptr);
+      sqlite3_result_text(
+          ctx, storage->GetString(opt_value->string_value).data(), -1, nullptr);
       break;
     case Variadic::kReal:
-      sqlite3_result_double(ctx, *args.real_value()[idx]);
+      sqlite3_result_double(ctx, opt_value->real_value);
       break;
   }
 }
@@ -636,6 +665,18 @@
   PERFETTO_CHECK(init_once);
 }
 
+void InsertIntoTraceMetricsTable(sqlite3* db, const std::string& metric_name) {
+  char* insert_sql = sqlite3_mprintf(
+      "INSERT INTO trace_metrics(name) VALUES('%q')", metric_name.c_str());
+  char* insert_error = nullptr;
+  sqlite3_exec(db, insert_sql, nullptr, nullptr, &insert_error);
+  sqlite3_free(insert_sql);
+  if (insert_error) {
+    PERFETTO_ELOG("Error registering table: %s", insert_error);
+    sqlite3_free(insert_error);
+  }
+}
+
 }  // namespace
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
@@ -699,10 +740,26 @@
       new ExperimentalSliceLayoutGenerator(
           context_.storage.get()->mutable_string_pool(),
           &storage->slice_table())));
-  RegisterDynamicTable(std::unique_ptr<AncestorSliceGenerator>(
-      new AncestorSliceGenerator(&context_)));
+  RegisterDynamicTable(std::unique_ptr<AncestorGenerator>(
+      new AncestorGenerator(AncestorGenerator::Ancestor::kSlice, &context_)));
+  RegisterDynamicTable(std::unique_ptr<AncestorGenerator>(new AncestorGenerator(
+      AncestorGenerator::Ancestor::kStackProfileCallsite, &context_)));
   RegisterDynamicTable(std::unique_ptr<DescendantSliceGenerator>(
       new DescendantSliceGenerator(&context_)));
+  RegisterDynamicTable(
+      std::unique_ptr<ConnectedFlowGenerator>(new ConnectedFlowGenerator(
+          ConnectedFlowGenerator::Direction::BOTH, &context_)));
+  RegisterDynamicTable(
+      std::unique_ptr<ConnectedFlowGenerator>(new ConnectedFlowGenerator(
+          ConnectedFlowGenerator::Direction::FOLLOWING, &context_)));
+  RegisterDynamicTable(
+      std::unique_ptr<ConnectedFlowGenerator>(new ConnectedFlowGenerator(
+          ConnectedFlowGenerator::Direction::PRECEDING, &context_)));
+  RegisterDynamicTable(std::unique_ptr<ExperimentalSchedUpidGenerator>(
+      new ExperimentalSchedUpidGenerator(storage->sched_slice_table(),
+                                         storage->thread_table())));
+  RegisterDynamicTable(std::unique_ptr<ThreadStateGenerator>(
+      new ThreadStateGenerator(&context_)));
 
   // New style db-backed tables.
   RegisterDbTable(storage->arg_table());
@@ -710,6 +767,7 @@
   RegisterDbTable(storage->process_table());
 
   RegisterDbTable(storage->slice_table());
+  RegisterDbTable(storage->flow_table());
   RegisterDbTable(storage->sched_slice_table());
   RegisterDbTable(storage->instant_table());
   RegisterDbTable(storage->gpu_slice_table());
@@ -753,6 +811,11 @@
   RegisterDbTable(storage->metadata_table());
   RegisterDbTable(storage->cpu_table());
   RegisterDbTable(storage->cpu_freq_table());
+
+  RegisterDbTable(storage->memory_snapshot_table());
+  RegisterDbTable(storage->process_memory_snapshot_table());
+  RegisterDbTable(storage->memory_snapshot_node_table());
+  RegisterDbTable(storage->memory_snapshot_edge_table());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() = default;
@@ -862,6 +925,16 @@
   sqlite3_interrupt(db_.get());
 }
 
+bool TraceProcessorImpl::IsRootMetricField(const std::string& metric_name) {
+  base::Optional<uint32_t> desc_idx =
+      pool_.FindDescriptorIdx(".perfetto.protos.TraceMetrics");
+  if (!desc_idx.has_value())
+    return false;
+  base::Optional<uint32_t> field_idx =
+      pool_.descriptors()[*desc_idx].FindFieldIdxByName(metric_name);
+  return field_idx.has_value();
+}
+
 util::Status TraceProcessorImpl::RegisterMetric(const std::string& path,
                                                 const std::string& sql) {
   std::string stripped_sql;
@@ -894,9 +967,14 @@
 
   metrics::SqlMetricFile metric;
   metric.path = path;
-  metric.proto_field_name = no_ext_name;
-  metric.output_table_name = no_ext_name + "_output";
   metric.sql = stripped_sql;
+
+  if (IsRootMetricField(no_ext_name)) {
+    metric.proto_field_name = no_ext_name;
+    metric.output_table_name = no_ext_name + "_output";
+    InsertIntoTraceMetricsTable(*db_, no_ext_name);
+  }
+
   sql_metrics_.emplace_back(metric);
   return util::OkStatus();
 }
@@ -942,6 +1020,33 @@
                                  root_descriptor, metrics_proto);
 }
 
+util::Status TraceProcessorImpl::ComputeMetricText(
+    const std::vector<std::string>& metric_names,
+    TraceProcessor::MetricResultFormat format,
+    std::string* metrics_string) {
+  std::vector<uint8_t> metrics_proto;
+  util::Status status = ComputeMetric(metric_names, &metrics_proto);
+  if (!status.ok())
+    return status;
+  switch (format) {
+    case TraceProcessor::MetricResultFormat::kProtoText:
+      *metrics_string = protozero_to_text::ProtozeroToText(
+          pool_, ".perfetto.protos.TraceMetrics",
+          protozero::ConstBytes{metrics_proto.data(), metrics_proto.size()},
+          protozero_to_text::kIncludeNewLines);
+      break;
+    case TraceProcessor::MetricResultFormat::kJson:
+      // TODO(dproy): Implement this.
+      PERFETTO_FATAL("Json formatted metrics not supported yet.");
+      break;
+  }
+  return status;
+}
+
+std::vector<uint8_t> TraceProcessorImpl::GetMetricDescriptors() {
+  return pool_.SerializeAsDescriptorSet();
+}
+
 void TraceProcessorImpl::EnableMetatrace() {
   metatrace::Enable();
 }
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 10432f1..cb9ef9f 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -64,6 +64,12 @@
   util::Status ComputeMetric(const std::vector<std::string>& metric_names,
                              std::vector<uint8_t>* metrics) override;
 
+  util::Status ComputeMetricText(const std::vector<std::string>& metric_names,
+                                 TraceProcessor::MetricResultFormat format,
+                                 std::string* metrics_string) override;
+
+  std::vector<uint8_t> GetMetricDescriptors() override;
+
   void InterruptQuery() override;
 
   size_t RestoreInitialTables() override;
@@ -92,6 +98,7 @@
                                  std::move(generator));
   }
 
+  bool IsRootMetricField(const std::string& metric_name);
   ScopedDb db_;
   std::unique_ptr<QueryCache> query_cache_;
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index c0d9718..fcf53bb 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -35,6 +35,8 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/version.h"
+#include "perfetto/profiling/deobfuscator.h"
 #include "perfetto/trace_processor/read_trace.h"
 #include "perfetto/trace_processor/trace_processor.h"
 #include "src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h"
@@ -46,13 +48,13 @@
 #include "src/trace_processor/rpc/httpd.h"
 #endif
 
+#include "src/profiling/symbolizer/local_symbolizer.h"
 #include "src/profiling/symbolizer/symbolize_database.h"
 #include "src/profiling/symbolizer/symbolizer.h"
-#include "src/profiling/symbolizer/local_symbolizer.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #define PERFETTO_HAS_SIGNAL_H() 1
 #else
 #define PERFETTO_HAS_SIGNAL_H() 0
@@ -64,23 +66,23 @@
 #include <sys/types.h>
 #endif
 
-#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
-#include "perfetto_version.gen.h"
-#else
-#define PERFETTO_GET_GIT_REVISION() "unknown"
-#endif
-
 #if PERFETTO_HAS_SIGNAL_H()
 #include <signal.h>
 #endif
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <io.h>
 #define ftruncate _chsize
 #else
 #include <dirent.h>
 #include <getopt.h>
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <unistd.h>  // For getuid() in GetConfigPath().
+#endif
+
 namespace perfetto {
 namespace trace_processor {
 
@@ -90,7 +92,7 @@
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
 
 bool EnsureDir(const std::string& path) {
-  return mkdir(path.c_str(), 0755) != -1 || errno == EEXIST;
+  return base::Mkdir(path) || errno == EEXIST;
 }
 
 bool EnsureFile(const std::string& path) {
@@ -99,8 +101,15 @@
 
 std::string GetConfigPath() {
   const char* homedir = getenv("HOME");
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   if (homedir == nullptr)
     homedir = getpwuid(getuid())->pw_dir;
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  if (homedir == nullptr)
+    homedir = getenv("USERPROFILE");
+#endif
   if (homedir == nullptr)
     return "";
   return std::string(homedir) + "/.config";
@@ -350,28 +359,11 @@
   google::protobuf::compiler::Parser parser;
   parser.Parse(&tokenizer, file_desc);
 
-  // Go through all the imports (dependencies) and make the import
-  // paths relative to the Perfetto root. This allows trace processor embedders
-  // to have paths relative to their own root for imports when using metric
-  // proto extensions.
-  for (int i = 0; i < file_desc->dependency_size(); ++i) {
-    static constexpr char kPrefix[] = "protos/perfetto/metrics/";
-    auto* dep = file_desc->mutable_dependency(i);
-
-    // If the file being imported contains kPrefix, it is probably an import of
-    // a Perfetto metrics proto. Strip anything before kPrefix to ensure that
-    // we resolve the paths correctly.
-    size_t idx = dep->find(kPrefix);
-    if (idx != std::string::npos) {
-      *dep = dep->substr(idx);
-    }
-  }
-
   file_desc->set_name(BaseName(extend_metrics_proto));
   pool->BuildFile(*file_desc);
 
   std::vector<uint8_t> metric_proto;
-  metric_proto.resize(static_cast<size_t>(desc_set.ByteSize()));
+  metric_proto.resize(desc_set.ByteSizeLong());
   desc_set.SerializeToArray(metric_proto.data(),
                             static_cast<int>(metric_proto.size()));
 
@@ -495,52 +487,6 @@
          static_cast<double>((t_end - t_start).count()) / 1E6);
 }
 
-void PrintShellUsage() {
-  PERFETTO_ELOG(
-      "Available commands:\n"
-      ".quit, .q    Exit the shell.\n"
-      ".help        This text.\n"
-      ".dump FILE   Export the trace as a sqlite database.\n"
-      ".reset       Destroys all tables/view created by the user.\n");
-}
-
-util::Status StartInteractiveShell(uint32_t column_width) {
-  SetupLineEditor();
-
-  for (;;) {
-    ScopedLine line = GetLine("> ");
-    if (!line)
-      break;
-    if (strcmp(line.get(), "") == 0) {
-      printf("If you want to quit either type .q or press CTRL-D (EOF)\n");
-      continue;
-    }
-    if (line.get()[0] == '.') {
-      char command[32] = {};
-      char arg[1024] = {};
-      sscanf(line.get() + 1, "%31s %1023s", command, arg);
-      if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
-        break;
-      } else if (strcmp(command, "help") == 0) {
-        PrintShellUsage();
-      } else if (strcmp(command, "dump") == 0 && strlen(arg)) {
-        if (!ExportTraceToDatabase(arg).ok())
-          PERFETTO_ELOG("Database export failed");
-      } else if (strcmp(command, "reset") == 0) {
-        g_tp->RestoreInitialTables();
-      } else {
-        PrintShellUsage();
-      }
-      continue;
-    }
-
-    base::TimeNanos t_start = base::GetWallTimeNs();
-    auto it = g_tp->ExecuteQuery(line.get());
-    PrintQueryResultInteractively(&it, t_start, column_width);
-  }
-  return util::OkStatus();
-}
-
 util::Status PrintQueryResultAsCsv(Iterator* it, FILE* output) {
   for (uint32_t c = 0; c < it->ColumnCount(); c++) {
     if (c > 0)
@@ -596,7 +542,7 @@
   while (!feof(input) && !ferror(input)) {
     std::string sql_query;
     while (fgets(buffer, sizeof(buffer), input)) {
-      std::string line = buffer;
+      std::string line = base::TrimLeading(buffer);
       if (IsBlankLine(line))
         break;
 
@@ -714,6 +660,7 @@
   std::string metric_names;
   std::string metric_output;
   std::string trace_file_path;
+  std::string port_number;
   bool launch_shell = false;
   bool enable_httpd = false;
   bool wide = false;
@@ -802,6 +749,7 @@
                                       This query is executed before the selected
                                       metrics and can't output any results.
  -D, --httpd                          Enables the HTTP RPC server.
+ --http-port PORT                     Specify what port to run HTTP RPC server.
  -i, --interactive                    Starts interactive mode even after a query
                                       file is specified with -q or
                                       --run-metrics.
@@ -832,9 +780,10 @@
     OPT_PRE_METRICS,
     OPT_METRICS_OUTPUT,
     OPT_FORCE_FULL_SORT,
+    OPT_HTTP_PORT,
   };
 
-  static const struct option long_options[] = {
+  static const option long_options[] = {
       {"help", no_argument, nullptr, 'h'},
       {"version", no_argument, nullptr, 'v'},
       {"wide", no_argument, nullptr, 'W'},
@@ -849,6 +798,7 @@
       {"pre-metrics", required_argument, nullptr, OPT_PRE_METRICS},
       {"metrics-output", required_argument, nullptr, OPT_METRICS_OUTPUT},
       {"full-sort", no_argument, nullptr, OPT_FORCE_FULL_SORT},
+      {"http-port", required_argument, nullptr, OPT_HTTP_PORT},
       {nullptr, 0, nullptr, 0}};
 
   bool explicit_interactive = false;
@@ -861,7 +811,7 @@
       break;  // EOF.
 
     if (option == 'v') {
-      printf("%s\n", PERFETTO_GET_GIT_REVISION());
+      printf("%s\n", base::GetVersionString());
       exit(0);
     }
 
@@ -929,6 +879,11 @@
       continue;
     }
 
+    if (option == OPT_HTTP_PORT) {
+      command_line_options.port_number = optarg;
+      continue;
+    }
+
     PrintUsage(argv);
     exit(option == 'h' ? 0 : 1);
   }
@@ -999,6 +954,21 @@
         });
     g_tp->NotifyEndOfFile();
   }
+
+  auto maybe_map = profiling::GetPerfettoProguardMapPath();
+  if (!maybe_map.empty()) {
+    profiling::ReadProguardMapsToDeobfuscationPackets(
+        maybe_map, [](const std::string& trace_proto) {
+          std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
+          memcpy(buf.get(), trace_proto.data(), trace_proto.size());
+          auto status = g_tp->Parse(std::move(buf), trace_proto.size());
+          if (!status.ok()) {
+            PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
+                                    status.message().c_str());
+            return;
+          }
+        });
+  }
   return util::OkStatus();
 }
 
@@ -1052,15 +1022,17 @@
       continue;
 
     std::string no_ext_name = metric_or_path.substr(0, ext_idx);
-    util::Status status = RegisterMetric(no_ext_name + ".sql");
+
+    // The proto must be extended before registering the metric.
+    util::Status status = ExtendMetricsProto(no_ext_name + ".proto", &pool);
     if (!status.ok()) {
-      return util::ErrStatus("Unable to register metric %s: %s",
+      return util::ErrStatus("Unable to extend metrics proto %s: %s",
                              metric_or_path.c_str(), status.c_message());
     }
 
-    status = ExtendMetricsProto(no_ext_name + ".proto", &pool);
+    status = RegisterMetric(no_ext_name + ".sql");
     if (!status.ok()) {
-      return util::ErrStatus("Unable to extend metrics proto %s: %s",
+      return util::ErrStatus("Unable to register metric %s: %s",
                              metric_or_path.c_str(), status.c_message());
     }
 
@@ -1080,6 +1052,58 @@
   return RunMetrics(std::move(metrics), format, pool);
 }
 
+void PrintShellUsage() {
+  PERFETTO_ELOG(
+      "Available commands:\n"
+      ".quit, .q    Exit the shell.\n"
+      ".help        This text.\n"
+      ".dump FILE   Export the trace as a sqlite database.\n"
+      ".read FILE   Executes the queries in the FILE.\n"
+      ".reset       Destroys all tables/view created by the user.\n");
+}
+
+util::Status StartInteractiveShell(uint32_t column_width) {
+  SetupLineEditor();
+
+  for (;;) {
+    ScopedLine line = GetLine("> ");
+    if (!line)
+      break;
+    if (strcmp(line.get(), "") == 0) {
+      printf("If you want to quit either type .q or press CTRL-D (EOF)\n");
+      continue;
+    }
+    if (line.get()[0] == '.') {
+      char command[32] = {};
+      char arg[1024] = {};
+      sscanf(line.get() + 1, "%31s %1023s", command, arg);
+      if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
+        break;
+      } else if (strcmp(command, "help") == 0) {
+        PrintShellUsage();
+      } else if (strcmp(command, "dump") == 0 && strlen(arg)) {
+        if (!ExportTraceToDatabase(arg).ok())
+          PERFETTO_ELOG("Database export failed");
+      } else if (strcmp(command, "reset") == 0) {
+        g_tp->RestoreInitialTables();
+      } else if (strcmp(command, "read") == 0 && strlen(arg)) {
+        util::Status status = RunQueries(arg, true);
+        if (!status.ok()) {
+          PERFETTO_ELOG("%s", status.c_message());
+        }
+      } else {
+        PrintShellUsage();
+      }
+      continue;
+    }
+
+    base::TimeNanos t_start = base::GetWallTimeNs();
+    auto it = g_tp->ExecuteQuery(line.get());
+    PrintQueryResultInteractively(&it, t_start, column_width);
+  }
+  return util::OkStatus();
+}
+
 util::Status TraceProcessorMain(int argc, char** argv) {
   CommandLineOptions options = ParseCommandLineOptions(argc, argv);
 
@@ -1110,7 +1134,7 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
   if (options.enable_httpd) {
-    RunHttpRPCServer(std::move(tp));
+    RunHttpRPCServer(std::move(tp), options.port_number);
     PERFETTO_FATAL("Should never return");
   }
 #endif
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 4ae3a64..ff9cee6 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -21,15 +21,17 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/default_modules.h"
 #include "src/trace_processor/importers/proto/args_table_utils.h"
+#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
 #include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/metadata_tracker.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
-#include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
 #include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_sorter.h"
@@ -41,16 +43,24 @@
   context_.config = cfg;
   context_.storage.reset(new TraceStorage(context_.config));
   context_.track_tracker.reset(new TrackTracker(&context_));
+  context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
   context_.args_tracker.reset(new ArgsTracker(&context_));
   context_.slice_tracker.reset(new SliceTracker(&context_));
+  context_.flow_tracker.reset(new FlowTracker(&context_));
   context_.event_tracker.reset(new EventTracker(&context_));
   context_.process_tracker.reset(new ProcessTracker(&context_));
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
+  context_.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
   context_.metadata_tracker.reset(new MetadataTracker(&context_));
   context_.global_args_tracker.reset(new GlobalArgsTracker(&context_));
   context_.proto_to_args_table_.reset(new ProtoToArgsTable(&context_));
 
+  context_.slice_tracker->SetOnSliceBeginCallback(
+      [this](TrackId track_id, SliceId slice_id) {
+        context_.flow_tracker->ClosePendingEventsOnTrack(track_id, slice_id);
+      });
+
   RegisterDefaultModules(&context_);
 }
 
@@ -87,6 +97,7 @@
   for (std::unique_ptr<ProtoImporterModule>& module : context_.modules) {
     module->NotifyEndOfFile();
   }
+  context_.args_tracker->Flush();
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index a42e9b0..7b3ae38 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -106,10 +106,12 @@
 
   inline void PushFtraceEvent(uint32_t cpu,
                               int64_t timestamp,
-                              TraceBlobView event) {
+                              TraceBlobView event,
+                              PacketSequenceState* state) {
     set_ftrace_batch_cpu_for_DCHECK(cpu);
-    GetQueue(cpu + 1)->Append(
-        TimestampedTracePiece(timestamp, packet_idx_++, std::move(event)));
+    GetQueue(cpu + 1)->Append(TimestampedTracePiece(
+        timestamp, packet_idx_++,
+        FtraceEventData{std::move(event), state->current_generation()}));
 
     // The caller must call FinalizeFtraceEventBatch() after having pushed a
     // batch of ftrace events. This is to amortize the overhead of handling
diff --git a/src/trace_processor/trace_sorter_unittest.cc b/src/trace_processor/trace_sorter_unittest.cc
index a7b2fc7..b49f515 100644
--- a/src/trace_processor/trace_sorter_unittest.cc
+++ b/src/trace_processor/trace_sorter_unittest.cc
@@ -49,9 +49,9 @@
                          int64_t timestamp,
                          TimestampedTracePiece ttp) override {
     bool isNonCompact = ttp.type == TimestampedTracePiece::Type::kFtraceEvent;
-    MOCK_ParseFtracePacket(cpu, timestamp,
-                           isNonCompact ? ttp.ftrace_event.data() : nullptr,
-                           isNonCompact ? ttp.ftrace_event.length() : 0);
+    MOCK_ParseFtracePacket(
+        cpu, timestamp, isNonCompact ? ttp.ftrace_event.event.data() : nullptr,
+        isNonCompact ? ttp.ftrace_event.event.length() : 0);
   }
 
   MOCK_METHOD3(MOCK_ParseTracePacket,
@@ -93,10 +93,11 @@
 };
 
 TEST_F(TraceSorterTest, TestFtrace) {
+  PacketSequenceState state(&context_);
   TraceBlobView view = test_buffer_.slice(0, 1);
   EXPECT_CALL(*parser_, MOCK_ParseFtracePacket(0, 1000, view.data(), 1));
   context_.sorter->PushFtraceEvent(0 /*cpu*/, 1000 /*timestamp*/,
-                                   std::move(view));
+                                   std::move(view), &state);
   context_.sorter->FinalizeFtraceEventBatch(0);
   context_.sorter->ExtractEventsForced();
 }
@@ -126,12 +127,12 @@
 
   context_.sorter->SetWindowSizeNs(200);
   context_.sorter->PushFtraceEvent(2 /*cpu*/, 1200 /*timestamp*/,
-                                   std::move(view_4));
+                                   std::move(view_4), &state);
   context_.sorter->FinalizeFtraceEventBatch(2);
   context_.sorter->PushTracePacket(1001, &state, std::move(view_2));
   context_.sorter->PushTracePacket(1100, &state, std::move(view_3));
   context_.sorter->PushFtraceEvent(0 /*cpu*/, 1000 /*timestamp*/,
-                                   std::move(view_1));
+                                   std::move(view_1), &state);
 
   context_.sorter->FinalizeFtraceEventBatch(0);
   context_.sorter->ExtractEventsForced();
@@ -159,13 +160,13 @@
 
   context_.sorter->SetWindowSizeNs(200);
   context_.sorter->PushFtraceEvent(2 /*cpu*/, 1200 /*timestamp*/,
-                                   std::move(view_4));
+                                   std::move(view_4), &state);
   context_.sorter->FinalizeFtraceEventBatch(2);
   context_.sorter->PushTracePacket(1001, &state, std::move(view_2));
   context_.sorter->PushTracePacket(1100, &state, std::move(view_3));
 
   context_.sorter->PushFtraceEvent(0 /*cpu*/, 1000 /*timestamp*/,
-                                   std::move(view_1));
+                                   std::move(view_1), &state);
   context_.sorter->FinalizeFtraceEventBatch(0);
 
   // At this point, we should just flush the 1000 and 1001 packets.
@@ -188,6 +189,7 @@
 // Tests that the output of the TraceSorter matches the timestamp order
 // (% events happening at the same time on different CPUs).
 TEST_F(TraceSorterTest, MultiQueueSorting) {
+  PacketSequenceState state(&context_);
   std::minstd_rand0 rnd_engine(0);
   std::map<int64_t /*ts*/, std::vector<uint32_t /*cpu*/>> expectations;
 
@@ -215,7 +217,8 @@
     for (int j = 0; j < num_cpus; j++) {
       uint32_t cpu = static_cast<uint32_t>(rnd_engine() % 32);
       expectations[ts].push_back(cpu);
-      context_.sorter->PushFtraceEvent(cpu, ts, TraceBlobView(nullptr, 0, 0));
+      context_.sorter->PushFtraceEvent(cpu, ts, TraceBlobView(nullptr, 0, 0),
+                                       &state);
       context_.sorter->FinalizeFtraceEventBatch(cpu);
     }
   }
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index ab9c016..f2a74b1 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -27,6 +27,7 @@
 namespace trace_processor {
 
 class ArgsTracker;
+class AsyncTrackSetTracker;
 class AndroidProbesTracker;
 class ChunkedTraceReader;
 class ClockTracker;
@@ -34,12 +35,14 @@
 class ForwardingTraceParser;
 class FtraceModule;
 class GlobalArgsTracker;
+class GlobalStackProfileTracker;
 class HeapGraphTracker;
 class HeapProfileTracker;
 class MetadataTracker;
 class ProtoImporterModule;
 class ProcessTracker;
 class SliceTracker;
+class FlowTracker;
 class TraceParser;
 class TraceSorter;
 class TraceStorage;
@@ -66,11 +69,14 @@
   std::unique_ptr<ArgsTracker> args_tracker;
 
   std::unique_ptr<TrackTracker> track_tracker;
+  std::unique_ptr<AsyncTrackSetTracker> async_track_set_tracker;
   std::unique_ptr<SliceTracker> slice_tracker;
+  std::unique_ptr<FlowTracker> flow_tracker;
   std::unique_ptr<ProcessTracker> process_tracker;
   std::unique_ptr<EventTracker> event_tracker;
   std::unique_ptr<ClockTracker> clock_tracker;
   std::unique_ptr<HeapProfileTracker> heap_profile_tracker;
+  std::unique_ptr<GlobalStackProfileTracker> global_stack_profile_tracker;
   std::unique_ptr<MetadataTracker> metadata_tracker;
 
   // These fields are stored as pointers to Destructible objects rather than
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 28f1781..efd3557 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -48,6 +48,7 @@
     "../../../gn:default_deps",
     "../../../include/perfetto/trace_processor",
     "../../../protos/perfetto/common:zero",
+    "../../../protos/perfetto/trace_processor:zero",
     "../../base",
     "../../protozero",
   ]
@@ -57,6 +58,7 @@
   sources = [ "protozero_to_text_unittests.cc" ]
   testonly = true
   deps = [
+    ":descriptors",
     ":protozero_to_text",
     "..:track_event_descriptor",
     "../../../gn:default_deps",
diff --git a/src/trace_processor/util/descriptors.cc b/src/trace_processor/util/descriptors.cc
index 61a7fe1..55bc525 100644
--- a/src/trace_processor/util/descriptors.cc
+++ b/src/trace_processor/util/descriptors.cc
@@ -17,8 +17,9 @@
 #include "src/trace_processor/util/descriptors.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/field.h"
-
+#include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/common/descriptor.pbzero.h"
+#include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -85,7 +86,22 @@
   return util::OkStatus();
 }
 
+void DescriptorPool::CheckPreviousDefinition(
+    const std::string& file_name,
+    const std::string& descriptor_name) {
+  auto prev_idx = FindDescriptorIdx(descriptor_name);
+  if (prev_idx.has_value()) {
+    auto prev_file = descriptors_[*prev_idx].file_name();
+    // We should already make sure we process each file once, so if we're
+    // hitting this path, it means the same message was defined in multiple
+    // files.
+    PERFETTO_FATAL("%s: %s was already defined in file %s", file_name.c_str(),
+                   descriptor_name.c_str(), prev_file.c_str());
+  }
+}
+
 void DescriptorPool::AddNestedProtoDescriptors(
+    const std::string& file_name,
     const std::string& package_name,
     base::Optional<uint32_t> parent_idx,
     protozero::ConstBytes descriptor_proto,
@@ -97,8 +113,10 @@
   auto full_name =
       parent_name + "." + base::StringView(decoder.name()).ToStdString();
 
+  CheckPreviousDefinition(file_name, full_name);
+
   using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
-  ProtoDescriptor proto_descriptor(package_name, full_name,
+  ProtoDescriptor proto_descriptor(file_name, package_name, full_name,
                                    ProtoDescriptor::Type::kMessage, parent_idx);
   for (auto it = decoder.field(); it; ++it) {
     FieldDescriptorProto::Decoder f_decoder(*it);
@@ -108,10 +126,10 @@
 
   auto idx = static_cast<uint32_t>(descriptors_.size()) - 1;
   for (auto it = decoder.enum_type(); it; ++it) {
-    AddEnumProtoDescriptors(package_name, idx, *it);
+    AddEnumProtoDescriptors(file_name, package_name, idx, *it);
   }
   for (auto it = decoder.nested_type(); it; ++it) {
-    AddNestedProtoDescriptors(package_name, idx, *it, extensions);
+    AddNestedProtoDescriptors(file_name, package_name, idx, *it, extensions);
   }
   for (auto ext_it = decoder.extension(); ext_it; ++ext_it) {
     extensions->emplace_back(package_name, *ext_it);
@@ -119,6 +137,7 @@
 }
 
 void DescriptorPool::AddEnumProtoDescriptors(
+    const std::string& file_name,
     const std::string& package_name,
     base::Optional<uint32_t> parent_idx,
     protozero::ConstBytes descriptor_proto) {
@@ -129,7 +148,9 @@
   auto full_name =
       parent_name + "." + base::StringView(decoder.name()).ToStdString();
 
-  ProtoDescriptor proto_descriptor(package_name, full_name,
+  CheckPreviousDefinition(file_name, full_name);
+
+  ProtoDescriptor proto_descriptor(file_name, package_name, full_name,
                                    ProtoDescriptor::Type::kEnum, base::nullopt);
   for (auto it = decoder.value(); it; ++it) {
     protos::pbzero::EnumValueDescriptorProto::Decoder enum_value(it->data(),
@@ -150,27 +171,30 @@
   std::vector<ExtensionInfo> extensions;
   for (auto it = proto.file(); it; ++it) {
     protos::pbzero::FileDescriptorProto::Decoder file(*it);
+    std::string file_name = file.name().ToStdString();
+    if (processed_files_.find(file_name) != processed_files_.end()) {
+      // This file has been loaded once already. Skip.
+      continue;
+    }
+    processed_files_.insert(file_name);
     std::string package = "." + base::StringView(file.package()).ToStdString();
     for (auto message_it = file.message_type(); message_it; ++message_it) {
-      AddNestedProtoDescriptors(package, base::nullopt, *message_it,
+      AddNestedProtoDescriptors(file_name, package, base::nullopt, *message_it,
                                 &extensions);
     }
     for (auto enum_it = file.enum_type(); enum_it; ++enum_it) {
-      AddEnumProtoDescriptors(package, base::nullopt, *enum_it);
+      AddEnumProtoDescriptors(file_name, package, base::nullopt, *enum_it);
     }
     for (auto ext_it = file.extension(); ext_it; ++ext_it) {
       extensions.emplace_back(package, *ext_it);
     }
   }
 
-  // Second pass: extract all the extension protos and add them to the real
-  // protos.
-  for (auto it = proto.file(); it; ++it) {
-    for (auto extension : extensions) {
-      auto status = AddExtensionField(extension.first, extension.second);
-      if (!status.ok())
-        return status;
-    }
+  // Second pass: Add extension fields to the real protos.
+  for (const auto& extension : extensions) {
+    auto status = AddExtensionField(extension.first, extension.second);
+    if (!status.ok())
+      return status;
   }
 
   // Third pass: resolve the types of all the fields to the correct indiices.
@@ -208,11 +232,38 @@
                                    : base::nullopt;
 }
 
-ProtoDescriptor::ProtoDescriptor(std::string package_name,
+std::vector<uint8_t> DescriptorPool::SerializeAsDescriptorSet() {
+  protozero::HeapBuffered<protos::pbzero::DescriptorSet> descs;
+  for (auto& desc : descriptors()) {
+    protos::pbzero::DescriptorProto* proto_descriptor =
+        descs->add_descriptors();
+    proto_descriptor->set_name(desc.full_name());
+    for (auto& field : desc.fields()) {
+      protos::pbzero::FieldDescriptorProto* field_descriptor =
+          proto_descriptor->add_field();
+      field_descriptor->set_name(field.name());
+      field_descriptor->set_number(static_cast<int32_t>(field.number()));
+      // We do not support required fields. They will show up as optional
+      // after serialization.
+      field_descriptor->set_label(
+          field.is_repeated()
+              ? protos::pbzero::FieldDescriptorProto::LABEL_REPEATED
+              : protos::pbzero::FieldDescriptorProto::LABEL_OPTIONAL);
+      field_descriptor->set_type_name(field.resolved_type_name());
+      field_descriptor->set_type(
+          static_cast<protos::pbzero::FieldDescriptorProto_Type>(field.type()));
+    }
+  }
+  return descs.SerializeAsArray();
+}
+
+ProtoDescriptor::ProtoDescriptor(std::string file_name,
+                                 std::string package_name,
                                  std::string full_name,
                                  Type type,
                                  base::Optional<uint32_t> parent_id)
-    : package_name_(std::move(package_name)),
+    : file_name_(std::move(file_name)),
+      package_name_(std::move(package_name)),
       full_name_(std::move(full_name)),
       type_(type),
       parent_id_(parent_id) {}
diff --git a/src/trace_processor/util/descriptors.h b/src/trace_processor/util/descriptors.h
index 990d316..aac5d47 100644
--- a/src/trace_processor/util/descriptors.h
+++ b/src/trace_processor/util/descriptors.h
@@ -18,6 +18,7 @@
 #define SRC_TRACE_PROCESSOR_UTIL_DESCRIPTORS_H_
 
 #include <algorithm>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -72,7 +73,8 @@
  public:
   enum class Type { kEnum = 0, kMessage = 1 };
 
-  ProtoDescriptor(std::string package_name,
+  ProtoDescriptor(std::string file_name,
+                  std::string package_name,
                   std::string full_name,
                   Type type,
                   base::Optional<uint32_t> parent_id);
@@ -119,6 +121,8 @@
                                     : base::Optional<std::string>(it->second);
   }
 
+  const std::string& file_name() const { return file_name_; }
+
   const std::string& package_name() const { return package_name_; }
 
   const std::string& full_name() const { return full_name_; }
@@ -127,6 +131,7 @@
   std::vector<FieldDescriptor>* mutable_fields() { return &fields_; }
 
  private:
+  std::string file_name_;  // File in which descriptor was originally defined.
   std::string package_name_;
   std::string full_name_;
   const Type type_;
@@ -150,15 +155,22 @@
     return descriptors_;
   }
 
+  std::vector<uint8_t> SerializeAsDescriptorSet();
+
  private:
-  void AddNestedProtoDescriptors(const std::string& package_name,
+  void AddNestedProtoDescriptors(const std::string& file_name,
+                                 const std::string& package_name,
                                  base::Optional<uint32_t> parent_idx,
                                  protozero::ConstBytes descriptor_proto,
                                  std::vector<ExtensionInfo>* extensions);
-  void AddEnumProtoDescriptors(const std::string& package_name,
+  void AddEnumProtoDescriptors(const std::string& file_name,
+                               const std::string& package_name,
                                base::Optional<uint32_t> parent_idx,
                                protozero::ConstBytes descriptor_proto);
 
+  void CheckPreviousDefinition(const std::string& file_name,
+                               const std::string& descriptor_name);
+
   util::Status AddExtensionField(const std::string& package_name,
                                  protozero::ConstBytes field_desc_proto);
 
@@ -168,6 +180,7 @@
                                             const std::string& short_type);
 
   std::vector<ProtoDescriptor> descriptors_;
+  std::set<std::string> processed_files_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/util/proto_to_json.cc b/src/trace_processor/util/proto_to_json.cc
index 6befd5e..1ab52fa 100644
--- a/src/trace_processor/util/proto_to_json.cc
+++ b/src/trace_processor/util/proto_to_json.cc
@@ -30,42 +30,6 @@
 
 namespace {
 
-std::string EscapeJsonString(const std::string& raw) {
-  std::string ret;
-  for (auto it = raw.cbegin(); it != raw.cend(); it++) {
-    switch (*it) {
-      case '\\':
-        ret += "\\\\";
-        break;
-      case '"':
-        ret += "\\\"";
-        break;
-      case '/':
-        ret += "\\/";
-        break;
-      case '\b':
-        ret += "\\b";
-        break;
-      case '\f':
-        ret += "\\f";
-        break;
-      case '\n':
-        ret += "\\n";
-        break;
-      case '\r':
-        ret += "\\r";
-        break;
-      case '\t':
-        ret += "\\t";
-        break;
-      default:
-        ret += *it;
-        break;
-    }
-  }
-  return '"' + ret + '"';
-}
-
 std::string FieldToJson(const google::protobuf::Message& message,
                         const google::protobuf::FieldDescriptor* field_desc,
                         int idx,
@@ -80,7 +44,7 @@
                                 ? ref->GetRepeatedBool(message, field_desc, idx)
                                 : ref->GetBool(message, field_desc));
     case FieldDescriptor::CppType::CPPTYPE_ENUM:
-      return EscapeJsonString(
+      return base::QuoteAndEscapeControlCodes(
           is_repeated ? ref->GetRepeatedEnum(message, field_desc, idx)->name()
                       : ref->GetEnum(message, field_desc)->name());
     case FieldDescriptor::CppType::CPPTYPE_FLOAT:
@@ -102,7 +66,7 @@
           is_repeated ? ref->GetRepeatedDouble(message, field_desc, idx)
                       : ref->GetDouble(message, field_desc));
     case FieldDescriptor::CppType::CPPTYPE_STRING:
-      return EscapeJsonString(
+      return base::QuoteAndEscapeControlCodes(
           is_repeated ? ref->GetRepeatedString(message, field_desc, idx)
                       : ref->GetString(message, field_desc));
     case FieldDescriptor::CppType::CPPTYPE_UINT32:
diff --git a/src/trace_processor/util/protozero_to_text.cc b/src/trace_processor/util/protozero_to_text.cc
index cc0f659..df7deb8 100644
--- a/src/trace_processor/util/protozero_to_text.cc
+++ b/src/trace_processor/util/protozero_to_text.cc
@@ -1,5 +1,6 @@
 #include "src/trace_processor/util/protozero_to_text.h"
 
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/proto_decoder.h"
 #include "perfetto/protozero/proto_utils.h"
@@ -11,8 +12,24 @@
 
 namespace perfetto {
 namespace trace_processor {
+namespace protozero_to_text {
+
 namespace {
 
+std::string BytesToHexEncodedString(const std::string& bytes) {
+  // Each byte becomes four chars 'A' -> "\x41" + 1 for trailing null.
+  std::string value(4 * bytes.size() + 1, 'Z');
+  for (size_t i = 0; i < bytes.size(); ++i) {
+    // snprintf prints 5 characters: '\x', then two hex digits, and finally a
+    // null byte. As we write left to right, we keep overwriting the null
+    // byte, except for the last call to snprintf.
+    snprintf(&(value[4 * i]), 5, "\\x%02hhx", bytes[i]);
+  }
+  // Trim trailing null.
+  value.resize(4 * bytes.size());
+  return value;
+}
+
 // Recursively determine the size of all the string like things passed in the
 // parameter pack |rest|.
 size_t SizeOfStr() {
@@ -53,7 +70,7 @@
                                            const protozero::Field& field,
                                            const std::string& separator,
                                            const std::string& indent,
-                                           DescriptorPool* pool,
+                                           const DescriptorPool& pool,
                                            std::string* out) {
   using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
   switch (fd.type()) {
@@ -97,15 +114,22 @@
       StrAppend(out, separator, indent, fd.name(), ": ",
                 std::to_string(field.as_float()));
       return;
-    case FieldDescriptorProto::TYPE_STRING:
-      StrAppend(out, separator, indent, fd.name(), ": ", field.as_std_string());
+    case FieldDescriptorProto::TYPE_STRING: {
+      auto s = base::QuoteAndEscapeControlCodes(field.as_std_string());
+      StrAppend(out, separator, indent, fd.name(), ": ", s);
       return;
+    }
+    case FieldDescriptorProto::TYPE_BYTES: {
+      std::string value = BytesToHexEncodedString(field.as_std_string());
+      StrAppend(out, separator, indent, fd.name(), ": \"", value, "\"");
+      return;
+    }
     case FieldDescriptorProto::TYPE_ENUM: {
       auto opt_enum_descriptor_idx =
-          pool->FindDescriptorIdx(fd.resolved_type_name());
+          pool.FindDescriptorIdx(fd.resolved_type_name());
       PERFETTO_DCHECK(opt_enum_descriptor_idx);
       auto opt_enum_string =
-          pool->descriptors()[*opt_enum_descriptor_idx].FindEnumString(
+          pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
               field.as_int32());
       PERFETTO_DCHECK(opt_enum_string);
       StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string);
@@ -134,24 +158,28 @@
 // |type| and will use |pool| to look up the |type|. All output will be placed
 // in |output| and between fields |separator| will be placed. When called for
 // |indents| will be increased by 2 spaces to improve readability.
-void ProtozeroToText(const std::string& type,
-                     protozero::ConstBytes protobytes,
-                     bool include_new_lines,
-                     DescriptorPool* pool,
-                     std::string* indents,
-                     std::string* output) {
-  auto opt_proto_descriptor_idx = pool->FindDescriptorIdx(type);
+void ProtozeroToTextInternal(const std::string& type,
+                             protozero::ConstBytes protobytes,
+                             NewLinesMode new_lines_mode,
+                             const DescriptorPool& pool,
+                             std::string* indents,
+                             std::string* output) {
+  auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type);
   PERFETTO_DCHECK(opt_proto_descriptor_idx);
-  auto& proto_descriptor = pool->descriptors()[*opt_proto_descriptor_idx];
+  auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx];
+  bool include_new_lines = new_lines_mode == kIncludeNewLines;
 
   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
   for (auto field = decoder.ReadField(); field.valid();
        field = decoder.ReadField()) {
-    // Since this is only used in debugging or tests we should always have a
-    // valid compiled in binary descriptor.
     auto opt_field_descriptor_idx =
         proto_descriptor.FindFieldIdxByTag(field.id());
-    PERFETTO_DCHECK(opt_field_descriptor_idx);
+    if (!opt_field_descriptor_idx) {
+      StrAppend(
+          output, output->empty() ? "" : "\n", *indents,
+          "# Ignoring unknown field with id: ", std::to_string(field.id()));
+      continue;
+    }
     const auto& field_descriptor =
         proto_descriptor.fields()[*opt_field_descriptor_idx];
 
@@ -165,8 +193,9 @@
         StrAppend(output, output->empty() ? "" : " ", field_descriptor.name(),
                   ": {");
       }
-      ProtozeroToText(field_descriptor.resolved_type_name(), field.as_bytes(),
-                      include_new_lines, pool, indents, output);
+      ProtozeroToTextInternal(field_descriptor.resolved_type_name(),
+                              field.as_bytes(), new_lines_mode, pool, indents,
+                              output);
       if (include_new_lines) {
         DecreaseIndents(indents);
         StrAppend(output, "\n", *indents, "}");
@@ -183,28 +212,36 @@
   PERFETTO_DCHECK(decoder.bytes_left() == 0);
 }
 
-std::string ProtozeroToText(const std::string& type,
+}  // namespace
+
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
                             protozero::ConstBytes protobytes,
-                            bool include_new_lines) {
+                            NewLinesMode new_lines_mode) {
   std::string indent = "";
   std::string final_result;
+  ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
+                          &final_result);
+  return final_result;
+}
+
+std::string DebugTrackEventProtozeroToText(const std::string& type,
+                                           protozero::ConstBytes protobytes) {
   DescriptorPool pool;
   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
                                               kTrackEventDescriptor.size());
   PERFETTO_DCHECK(status.ok());
-  ProtozeroToText(type, protobytes, include_new_lines, &pool, &indent,
-                  &final_result);
-  return final_result;
+  return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
 }
-}  // namespace
 
-std::string DebugProtozeroToText(const std::string& type,
-                                 protozero::ConstBytes protobytes) {
-  return ProtozeroToText(type, protobytes, /* include_new_lines = */ true);
-}
-std::string ShortDebugProtozeroToText(const std::string& type,
-                                      protozero::ConstBytes protobytes) {
-  return ProtozeroToText(type, protobytes, /* include_new_lines = */ false);
+std::string ShortDebugTrackEventProtozeroToText(
+    const std::string& type,
+    protozero::ConstBytes protobytes) {
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  PERFETTO_DCHECK(status.ok());
+  return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
 }
 
 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
@@ -226,5 +263,19 @@
   return *opt_enum_string;
 }
 
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
+                            const std::vector<uint8_t>& protobytes,
+                            NewLinesMode new_lines_mode) {
+  return ProtozeroToText(
+      pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
+      new_lines_mode);
+}
+
+std::string BytesToHexEncodedStringForTesting(const std::string& s) {
+  return BytesToHexEncodedString(s);
+}
+
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/protozero_to_text.h b/src/trace_processor/util/protozero_to_text.h
index e418926..c2082f9 100644
--- a/src/trace_processor/util/protozero_to_text.h
+++ b/src/trace_processor/util/protozero_to_text.h
@@ -24,15 +24,41 @@
 namespace perfetto {
 namespace trace_processor {
 
+class DescriptorPool;
+
+namespace protozero_to_text {
+
+enum NewLinesMode {
+  kIncludeNewLines = 0,
+  kSkipNewLines,
+};
+
 // Given a protozero message |protobytes| which is of fully qualified name
-// |type|. We will convert this into a text proto format string.
+// |type| within TrackEvent proto messages, we will convert this into a text
+// proto format string.
 //
-// DebugProtozeroToText will use new lines between fields, and
-// ShortDebugProtozeroToText will use only a single space.
-std::string DebugProtozeroToText(const std::string& type,
-                                 protozero::ConstBytes protobytes);
-std::string ShortDebugProtozeroToText(const std::string& type,
-                                      protozero::ConstBytes protobytes);
+// DebugTrackEventProtozeroToText will use new lines between fields, and
+// ShortDebugTrackEventProtozeroToText will use only a single space.
+std::string DebugTrackEventProtozeroToText(const std::string& type,
+                                           protozero::ConstBytes protobytes);
+std::string ShortDebugTrackEventProtozeroToText(
+    const std::string& type,
+    protozero::ConstBytes protobytes);
+
+// Given a protozero message |protobytes| which is of fully qualified name
+// |type|, convert this into a text proto format string. All types used in
+// message definition of |type| must be available in |pool|. If
+// |new_lines_modes| == kIncludeNewLines, new lines will be used between fields,
+// otherwise only a space will be used.
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
+                            protozero::ConstBytes protobytes,
+                            NewLinesMode new_lines_mode);
+
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
+                            const std::vector<uint8_t>& protobytes,
+                            NewLinesMode new_lines_mode);
 
 // Allow the conversion from a protozero enum to a string. The template is just
 // to allow easy enum passing since we will do the explicit cast to a int32_t
@@ -43,6 +69,9 @@
   return ProtozeroEnumToText(type, static_cast<int32_t>(enum_value));
 }
 
+std::string BytesToHexEncodedStringForTesting(const std::string&);
+
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/util/protozero_to_text_unittests.cc b/src/trace_processor/util/protozero_to_text_unittests.cc
index e172dd6..089f53d 100644
--- a/src/trace_processor/util/protozero_to_text_unittests.cc
+++ b/src/trace_processor/util/protozero_to_text_unittests.cc
@@ -19,10 +19,14 @@
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+#include "src/trace_processor/importers/proto/track_event.descriptor.h"
+#include "src/trace_processor/util/descriptors.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
+namespace protozero_to_text {
+
 namespace {
 
 constexpr size_t kChunkSize = 42;
@@ -36,13 +40,14 @@
   msg->set_track_uuid(4);
   msg->set_timestamp_delta_us(3);
   auto binary_proto = msg.SerializeAsArray();
-  EXPECT_EQ("track_uuid: 4\ntimestamp_delta_us: 3",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+  EXPECT_EQ(
+      "track_uuid: 4\ntimestamp_delta_us: 3",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
   EXPECT_EQ(
       "track_uuid: 4 timestamp_delta_us: 3",
-      ShortDebugProtozeroToText(
+      ShortDebugTrackEventProtozeroToText(
           ".perfetto.protos.TrackEvent",
           protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 }
@@ -60,7 +65,8 @@
   msg->set_timestamp_delta_us(3);
   auto binary_proto = msg.SerializeAsArray();
 
-  EXPECT_EQ(R"(track_uuid: 4
+  EXPECT_EQ(
+      R"(track_uuid: 4
 cc_scheduler_state: {
   deadline_us: 7
   state_machine: {
@@ -71,15 +77,15 @@
   observing_begin_frame_source: true
 }
 timestamp_delta_us: 3)",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 
   EXPECT_EQ(
       "track_uuid: 4 cc_scheduler_state: { deadline_us: 7 state_machine: { "
       "minor_state: { commit_count: 8 } } observing_begin_frame_source: true } "
       "timestamp_delta_us: 3",
-      ShortDebugProtozeroToText(
+      ShortDebugTrackEventProtozeroToText(
           ".perfetto.protos.TrackEvent",
           protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 }
@@ -89,14 +95,75 @@
   protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
   msg->set_type(TrackEvent::TYPE_SLICE_BEGIN);
   auto binary_proto = msg.SerializeAsArray();
-  EXPECT_EQ("type: TYPE_SLICE_BEGIN",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
-  EXPECT_EQ("type: TYPE_SLICE_BEGIN",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+  EXPECT_EQ(
+      "type: TYPE_SLICE_BEGIN",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
+  EXPECT_EQ(
+      "type: TYPE_SLICE_BEGIN",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
+}
+
+TEST(ProtozeroToTextTest, CustomDescriptorPoolBasic) {
+  using perfetto::protos::pbzero::TrackEvent;
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  msg->set_track_uuid(4);
+  msg->set_timestamp_delta_us(3);
+  auto binary_proto = msg.SerializeAsArray();
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ("track_uuid: 4\ntimestamp_delta_us: 3",
+            ProtozeroToText(pool, ".perfetto.protos.TrackEvent", binary_proto,
+                            kIncludeNewLines));
+  EXPECT_EQ("track_uuid: 4 timestamp_delta_us: 3",
+            ProtozeroToText(pool, ".perfetto.protos.TrackEvent", binary_proto,
+                            kSkipNewLines));
+}
+
+TEST(ProtozeroToTextTest, CustomDescriptorPoolNestedMsg) {
+  using perfetto::protos::pbzero::TrackEvent;
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  msg->set_track_uuid(4);
+  auto* state = msg->set_cc_scheduler_state();
+  state->set_deadline_us(7);
+  auto* machine = state->set_state_machine();
+  auto* minor_state = machine->set_minor_state();
+  minor_state->set_commit_count(8);
+  state->set_observing_begin_frame_source(true);
+  msg->set_timestamp_delta_us(3);
+  auto binary_proto = msg.SerializeAsArray();
+
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+
+  EXPECT_EQ(
+      R"(track_uuid: 4
+cc_scheduler_state: {
+  deadline_us: 7
+  state_machine: {
+    minor_state: {
+      commit_count: 8
+    }
+  }
+  observing_begin_frame_source: true
+}
+timestamp_delta_us: 3)",
+      ProtozeroToText(pool, ".perfetto.protos.TrackEvent", binary_proto,
+                      kIncludeNewLines));
+
+  EXPECT_EQ(
+      "track_uuid: 4 cc_scheduler_state: { deadline_us: 7 state_machine: { "
+      "minor_state: { commit_count: 8 } } observing_begin_frame_source: true } "
+      "timestamp_delta_us: 3",
+      ProtozeroToText(pool, ".perfetto.protos.TrackEvent", binary_proto,
+                      kSkipNewLines));
 }
 
 TEST(ProtozeroToTextTest, EnumToString) {
@@ -105,6 +172,48 @@
             ProtozeroEnumToText(".perfetto.protos.TrackEvent.Type",
                                 TrackEvent::TYPE_SLICE_END));
 }
+
+TEST(ProtozeroToTextTest, UnknownField) {
+  using perfetto::protos::pbzero::TrackEvent;
+  // Wrong type to force unknown field:
+  const auto type = ".perfetto.protos.ChromeCompositorSchedulerState";
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  auto* state = msg->set_cc_scheduler_state();
+  state->set_deadline_us(7);
+  auto* machine = state->set_state_machine();
+  auto* minor_state = machine->set_minor_state();
+  minor_state->set_commit_count(8);
+  auto bytes = msg.SerializeAsArray();
+
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+  ASSERT_EQ(ProtozeroToText(pool, type, bytes, kIncludeNewLines),
+            "# Ignoring unknown field with id: 24");
+}
+
+TEST(ProtozeroToTextTest, StringField) {
+  using perfetto::protos::pbzero::TrackEvent;
+  // Wrong type to force unknown field:
+  const auto type = ".perfetto.protos.TrackEvent";
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  msg->add_categories(R"(Hello, "World")");
+  auto bytes = msg.SerializeAsArray();
+
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+  ASSERT_EQ(ProtozeroToText(pool, type, bytes, kIncludeNewLines),
+            "categories: \"Hello, \\\"World\\\"\"");
+}
+
+TEST(ProtozeroToTextTest, BytesField) {
+  EXPECT_EQ(BytesToHexEncodedStringForTesting("abc"), R"(\x61\x62\x63)");
+}
+
 }  // namespace
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/traced/probes/BUILD.gn b/src/traced/probes/BUILD.gn
index 7eec236..d54e965 100644
--- a/src/traced/probes/BUILD.gn
+++ b/src/traced/probes/BUILD.gn
@@ -14,6 +14,8 @@
 
 import("../../../gn/test.gni")
 
+assert(target_os != "win")
+
 # The unprivileged daemon that is allowed to access tracefs (for ftrace).
 # Registers as a Producer on the traced daemon.
 executable("traced_probes") {
@@ -35,9 +37,6 @@
     "../../../gn:default_deps",
     "../../tracing/ipc/producer",
   ]
-  if (enable_perfetto_version_gen) {
-    deps += [ "//gn/standalone:gen_git_revision" ]
-  }
   sources = [ "probes.cc" ]
 }
 
diff --git a/src/traced/probes/android_log/android_log_data_source.cc b/src/traced/probes/android_log/android_log_data_source.cc
index f63ca51..286d138 100644
--- a/src/traced/probes/android_log/android_log_data_source.cc
+++ b/src/traced/probes/android_log/android_log_data_source.cc
@@ -41,7 +41,7 @@
 using protos::pbzero::AndroidLogConfig;
 using protos::pbzero::AndroidLogId;
 
-constexpr size_t kBufSize = base::kPageSize;
+constexpr size_t kBufSize = 4096;
 const char kLogTagsPath[] = "/system/etc/event-log-tags";
 const char kLogdrSocket[] = "/dev/socket/logdr";
 
@@ -208,7 +208,7 @@
   // modulo is to increase the chance that the wakeup is packed together with
   // some other wakeup task of traced_probes.
   const uint32_t kBatchMs = 100;
-  uint32_t delay_ms = kBatchMs - (now_ms % kBatchMs);
+  uint32_t delay_ms = kBatchMs - static_cast<uint32_t>(now_ms % kBatchMs);
   auto weak_this = weak_factory_.GetWeakPtr();
   task_runner_->PostDelayedTask(
       [weak_this] {
diff --git a/src/traced/probes/common/cpu_freq_info_for_testing.cc b/src/traced/probes/common/cpu_freq_info_for_testing.cc
index d1e2b76..132f75a 100644
--- a/src/traced/probes/common/cpu_freq_info_for_testing.cc
+++ b/src/traced/probes/common/cpu_freq_info_for_testing.cc
@@ -16,9 +16,6 @@
 
 #include "src/traced/probes/common/cpu_freq_info_for_testing.h"
 
-#include <dirent.h>
-#include <sys/stat.h>
-
 #include <algorithm>
 #include <memory>
 
@@ -65,10 +62,10 @@
 
 CpuFreqInfoForTesting::~CpuFreqInfoForTesting() {
   for (auto path : files_to_remove_)
-    RmFile(path);
+    PERFETTO_CHECK(remove(AbsolutePath(path).c_str()) == 0);
   std::reverse(dirs_to_remove_.begin(), dirs_to_remove_.end());
   for (auto path : dirs_to_remove_)
-    RmDir(path);
+    base::Rmdir(AbsolutePath(path));
 }
 
 std::unique_ptr<CpuFreqInfo> CpuFreqInfoForTesting::GetInstance() {
@@ -77,7 +74,7 @@
 
 void CpuFreqInfoForTesting::AddDir(std::string path) {
   dirs_to_remove_.push_back(path);
-  mkdir(AbsolutePath(path).c_str(), 0755);
+  base::Mkdir(AbsolutePath(path).c_str());
 }
 
 void CpuFreqInfoForTesting::AddFile(std::string path, std::string content) {
@@ -88,14 +85,6 @@
                  static_cast<ssize_t>(content.size()));
 }
 
-void CpuFreqInfoForTesting::RmDir(std::string path) {
-  PERFETTO_CHECK(rmdir(AbsolutePath(path).c_str()) == 0);
-}
-
-void CpuFreqInfoForTesting::RmFile(std::string path) {
-  PERFETTO_CHECK(remove(AbsolutePath(path).c_str()) == 0);
-}
-
 std::string CpuFreqInfoForTesting::AbsolutePath(std::string path) {
   return fake_cpu_dir_.path() + "/" + path;
 }
diff --git a/src/traced/probes/filesystem/inode_file_data_source.cc b/src/traced/probes/filesystem/inode_file_data_source.cc
index bbe38e0..5029675 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source.cc
@@ -60,20 +60,20 @@
   StaticMapDelegate(
       std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map)
       : map_(map) {}
-  ~StaticMapDelegate() {}
+  ~StaticMapDelegate() override {}
 
  private:
   bool OnInodeFound(BlockDeviceID block_device_id,
                     Inode inode_number,
                     const std::string& path,
-                    InodeFileMap_Entry_Type type) {
+                    InodeFileMap_Entry_Type type) override {
     std::unordered_map<Inode, InodeMapValue>& inode_map =
         (*map_)[block_device_id];
     inode_map[inode_number].SetType(type);
     inode_map[inode_number].AddPath(path);
     return true;
   }
-  void OnInodeScanDone() {}
+  void OnInodeScanDone() override {}
   std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>* map_;
 };
 
diff --git a/src/traced/probes/filesystem/prefix_finder.cc b/src/traced/probes/filesystem/prefix_finder.cc
index b22532e..4dca5c8 100644
--- a/src/traced/probes/filesystem/prefix_finder.cc
+++ b/src/traced/probes/filesystem/prefix_finder.cc
@@ -27,11 +27,21 @@
 }
 
 PrefixFinder::Node* PrefixFinder::Node::AddChild(std::string name) {
-  return children_.Emplace(std::move(name), this);
+  auto it = children_.emplace(std::move(name), this);
+  return const_cast<Node*>(&(*it.first));
 }
 
 PrefixFinder::Node* PrefixFinder::Node::MaybeChild(const std::string& name) {
-  return children_.Get(name);
+  // This will be nicer with C++14 transparent comparators.
+  // Then we will be able to look up by just the key using a sutiable
+  // comparator.
+  //
+  // For now we need to allow to construct Node from the key.
+  Node node(name);
+  auto it = children_.find(node);
+  if (it == children_.end())
+    return nullptr;
+  return const_cast<Node*>(&(*it));
 }
 
 PrefixFinder::PrefixFinder(size_t limit) : limit_(limit) {}
diff --git a/src/traced/probes/filesystem/prefix_finder.h b/src/traced/probes/filesystem/prefix_finder.h
index 3d84ed1..d2b8385 100644
--- a/src/traced/probes/filesystem/prefix_finder.h
+++ b/src/traced/probes/filesystem/prefix_finder.h
@@ -25,7 +25,6 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
-#include "perfetto/ext/base/lookup_set.h"
 
 namespace perfetto {
 
@@ -70,10 +69,17 @@
 
     const std::string name_;
     const Node* parent_;
-    base::LookupSet<Node, const std::string, &Node::name_> children_;
+    class NodeComparator {
+     public:
+      bool operator()(const Node& one, const Node& other) const {
+        return one.name_ < other.name_;
+      }
+    };
+
+    std::set<Node, NodeComparator> children_;
   };
 
-  PrefixFinder(size_t limit);
+  explicit PrefixFinder(size_t limit);
 
   // Add path to prefix mapping.
   // Must be called in DFS order.
diff --git a/src/traced/probes/ftrace/BUILD.gn b/src/traced/probes/ftrace/BUILD.gn
index 0ffd645..623323f 100644
--- a/src/traced/probes/ftrace/BUILD.gn
+++ b/src/traced/probes/ftrace/BUILD.gn
@@ -17,6 +17,8 @@
 import("../../../../gn/proto_library.gni")
 import("../../../../gn/test.gni")
 
+assert(target_os != "win")
+
 # For use_libfuzzer.
 if (perfetto_root_path == "//") {
   import("//gn/standalone/sanitizers/vars.gni")
@@ -43,7 +45,6 @@
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
-    ":format_parser",
     ":ftrace",
     ":test_messages_cpp",
     ":test_messages_lite",
@@ -56,17 +57,19 @@
     "../../../../protos/perfetto/trace/ftrace:zero",
     "../../../base:test_support",
     "../../../tracing/test:test_support",
+    "format_parser",
+    "format_parser:unittests",
   ]
   sources = [
     "cpu_reader_unittest.cc",
     "cpu_stats_parser_unittest.cc",
     "discover_vendor_tracepoints_unittest.cc",
     "event_info_unittest.cc",
-    "format_parser_unittest.cc",
     "ftrace_config_muxer_unittest.cc",
     "ftrace_config_unittest.cc",
     "ftrace_controller_unittest.cc",
     "ftrace_procfs_unittest.cc",
+    "printk_formats_parser_unittest.cc",
     "proto_translation_table_unittest.cc",
   ]
 }
@@ -103,14 +106,17 @@
     "../../../tracing/core",
   ]
   deps = [
-    ":format_parser",
     "..:data_source",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/traced",
     "../../../../protos/perfetto/trace:zero",
+    "../../../../protos/perfetto/trace/interned_data:zero",
+    "../../../../protos/perfetto/trace/profiling:zero",
     "../../../android_internal:lazy_library_loader",
     "../../../base",
+    "../../../kallsyms",
     "../../../protozero",
+    "format_parser",
   ]
   sources = [
     "atrace_hal_wrapper.cc",
@@ -142,22 +148,13 @@
     "ftrace_procfs.h",
     "ftrace_stats.cc",
     "ftrace_stats.h",
+    "printk_formats_parser.cc",
+    "printk_formats_parser.h",
     "proto_translation_table.cc",
     "proto_translation_table.h",
   ]
 }
 
-source_set("format_parser") {
-  deps = [
-    "../../../../gn:default_deps",
-    "../../../base",
-  ]
-  sources = [
-    "format_parser.cc",
-    "format_parser.h",
-  ]
-}
-
 if (enable_perfetto_benchmarks) {
   source_set("benchmarks") {
     testonly = true
diff --git a/src/traced/probes/ftrace/compact_sched.cc b/src/traced/probes/ftrace/compact_sched.cc
index 35309d8..cd2d117 100644
--- a/src/traced/probes/ftrace/compact_sched.cc
+++ b/src/traced/probes/ftrace/compact_sched.cc
@@ -208,7 +208,7 @@
   return CompactSchedConfig{/*enabled=*/false};
 }
 
-// Sanity check size of stack-allocated bundle state.
+// Check size of stack-allocated bundle state.
 static_assert(sizeof(CompactSchedBuffer) <= 1 << 18,
               "CompactSchedBuffer's on-stack size excessively large.");
 
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 6010e90..eaba2b2 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -17,8 +17,10 @@
 #include "src/traced/probes/ftrace/cpu_reader.h"
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <signal.h>
 
+#include <algorithm>
 #include <utility>
 
 #include "perfetto/base/build_config.h"
@@ -27,15 +29,20 @@
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
-#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
-#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
-#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "src/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/lazy_kernel_symbolizer.h"
 #include "src/traced/probes/ftrace/ftrace_config_muxer.h"
 #include "src/traced/probes/ftrace/ftrace_controller.h"
 #include "src/traced/probes/ftrace/ftrace_data_source.h"
 #include "src/traced/probes/ftrace/proto_translation_table.h"
 
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
 namespace perfetto {
 namespace {
 
@@ -140,8 +147,12 @@
 
 CpuReader::CpuReader(size_t cpu,
                      const ProtoTranslationTable* table,
+                     LazyKernelSymbolizer* symbolizer,
                      base::ScopedFile trace_fd)
-    : cpu_(cpu), table_(table), trace_fd_(std::move(trace_fd)) {
+    : cpu_(cpu),
+      table_(table),
+      symbolizer_(symbolizer),
+      trace_fd_(std::move(trace_fd)) {
   PERFETTO_CHECK(trace_fd_);
   PERFETTO_CHECK(SetBlocking(*trace_fd_, false));
 }
@@ -201,8 +212,11 @@
         // Expected errors:
         // EAGAIN: no data (since we're in non-blocking mode).
         // ENONMEM, EBUSY: temporary ftrace failures (they happen).
-        if (errno != EAGAIN && errno != ENOMEM && errno != EBUSY)
+        // ENODEV: the cpu is offline (b/145583318).
+        if (errno != EAGAIN && errno != ENOMEM && errno != EBUSY &&
+            errno != ENODEV) {
           PERFETTO_PLOG("Unexpected error on raw ftrace read");
+        }
         break;  // stop reading regardless of errno
       }
 
@@ -261,7 +275,8 @@
   for (FtraceDataSource* data_source : started_data_sources) {
     bool success = ProcessPagesForDataSource(
         data_source->trace_writer(), data_source->mutable_metadata(), cpu_,
-        data_source->parsing_config(), parsing_buf, pages_read, table_);
+        data_source->parsing_config(), parsing_buf, pages_read, table_,
+        symbolizer_);
     PERFETTO_CHECK(success);
   }
 
@@ -276,20 +291,88 @@
     const FtraceDataSourceConfig* ds_config,
     const uint8_t* parsing_buf,
     const size_t pages_read,
-    const ProtoTranslationTable* table) {
-  // Begin an FtraceEventBundle, and allocate the buffer for compact scheduler
-  // events (which will be unused if the compact option isn't enabled).
+    const ProtoTranslationTable* table,
+    LazyKernelSymbolizer* symbolizer) {
+  // Allocate the buffer for compact scheduler events (which will be unused if
+  // the compact option isn't enabled).
   CompactSchedBuffer compact_sched;
-  auto packet = trace_writer->NewTracePacket();
-  auto* bundle = packet->set_ftrace_events();
-
   bool compact_sched_enabled = ds_config->compact_sched.enabled;
 
-  // Note: The fastpath in proto_trace_parser.cc speculates on the fact
-  // that the cpu field is the first field of the proto message. If this
-  // changes, change proto_trace_parser.cc accordingly.
-  bundle->set_cpu(static_cast<uint32_t>(cpu));
+  TraceWriter::TracePacketHandle packet;
+  protos::pbzero::FtraceEventBundle* bundle = nullptr;
 
+  // This function is called after the contents of a FtraceBundle are written.
+  auto finalize_cur_packet = [&] {
+    PERFETTO_DCHECK(packet);
+    if (compact_sched_enabled)
+      compact_sched.WriteAndReset(bundle);
+
+    bundle->Finalize();
+    bundle = nullptr;
+
+    // Write the kernel symbol index (mangled address) -> name table.
+    // |metadata| is shared across all cpus, is distinct per |data_source| (i.e.
+    // tracing session) and is cleared after each FtraceController::ReadTick().
+    // const size_t kaddrs_size = metadata->kernel_addrs.size();
+    if (ds_config->symbolize_ksyms) {
+      // Symbol indexes are assigned mononically as |kernel_addrs.size()|,
+      // starting from index 1 (no symbol has index 0). Here we remember the
+      // size() (which is also == the highest value in |kernel_addrs|) at the
+      // beginning and only write newer indexes bigger than that.
+      uint32_t max_index_at_start = metadata->last_kernel_addr_index_written;
+      PERFETTO_DCHECK(max_index_at_start <= metadata->kernel_addrs.size());
+      protos::pbzero::InternedData* interned_data = nullptr;
+      auto* ksyms_map = symbolizer->GetOrCreateKernelSymbolMap();
+      for (const FtraceMetadata::KernelAddr& kaddr : metadata->kernel_addrs) {
+        if (kaddr.index <= max_index_at_start)
+          continue;
+        std::string sym_name = ksyms_map->Lookup(kaddr.addr);
+        if (sym_name.empty()) {
+          // Lookup failed. This can genuinely happen in many occasions. E.g.,
+          // workqueue_execute_start has two pointers: one is a pointer to a
+          // function (which we expect to be symbolized), the other (|work|) is
+          // a pointer to a heap struct, which is unsymbolizable, even when
+          // using the textual ftrace endpoint.
+          continue;
+        }
+
+        if (!interned_data) {
+          // If this is the very first write, clear the start of the sequence
+          // so the trace processor knows that all previous indexes can be
+          // discarded and that the mapping is restarting.
+          // In most cases this occurs with cpu==0. But if cpu0 is idle, this
+          // will happen with the first CPU that has any ftrace data.
+          if (max_index_at_start == 0) {
+            packet->set_sequence_flags(
+                protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+          }
+          interned_data = packet->set_interned_data();
+        }
+        auto* interned_sym = interned_data->add_kernel_symbols();
+        interned_sym->set_iid(kaddr.index);
+        interned_sym->set_str(sym_name);
+      }
+      auto max_it_at_end = static_cast<uint32_t>(metadata->kernel_addrs.size());
+      metadata->last_kernel_addr_index_written = max_it_at_end;
+    }
+
+    packet->Finalize();
+  };  // finalize_cur_packet().
+
+  auto start_new_packet = [&](bool lost_events) {
+    if (packet)
+      finalize_cur_packet();
+    packet = trace_writer->NewTracePacket();
+    bundle = packet->set_ftrace_events();
+    // Note: The fastpath in proto_trace_parser.cc speculates on the fact
+    // that the cpu field is the first field of the proto message. If this
+    // changes, change proto_trace_parser.cc accordingly.
+    bundle->set_cpu(static_cast<uint32_t>(cpu));
+    if (lost_events)
+      bundle->set_lost_events(true);
+  };
+
+  start_new_packet(/*lost_events=*/false);
   for (size_t i = 0; i < pages_read; i++) {
     const uint8_t* curr_page = parsing_buf + (i * base::kPageSize);
     const uint8_t* curr_page_end = curr_page + base::kPageSize;
@@ -315,17 +398,9 @@
         compact_sched_enabled &&
         compact_sched.interner().interned_comms_size() >
             kCompactSchedInternerThreshold;
-    if (page_header->lost_events || interner_past_threshold) {
-      if (compact_sched_enabled)
-        compact_sched.WriteAndReset(bundle);
-      packet->Finalize();
 
-      packet = trace_writer->NewTracePacket();
-      bundle = packet->set_ftrace_events();
-      bundle->set_cpu(static_cast<uint32_t>(cpu));
-      if (page_header->lost_events)
-        bundle->set_lost_events(true);
-    }
+    if (page_header->lost_events || interner_past_threshold)
+      start_new_packet(page_header->lost_events);
 
     size_t evt_size =
         ParsePagePayload(parse_pos, &page_header.value(), table, ds_config,
@@ -335,9 +410,7 @@
     // (FtraceMetadata -> FtraceStats in trace).
     PERFETTO_DCHECK(evt_size == page_header->size);
   }
-
-  if (compact_sched_enabled)
-    compact_sched.WriteAndReset(bundle);
+  finalize_cur_packet();
 
   return true;
 }
@@ -549,7 +622,7 @@
 
   bool success = true;
   for (const Field& field : table->common_fields())
-    success &= ParseField(field, start, end, message, metadata);
+    success &= ParseField(field, start, end, table, message, metadata);
 
   protozero::Message* nested =
       message->BeginNestedMessage<protozero::Message>(info.proto_field_id);
@@ -564,11 +637,11 @@
       // TODO(taylori): Avoid outputting field names every time.
       generic_field->AppendString(GenericFtraceEvent::Field::kNameFieldNumber,
                                   field.ftrace_name);
-      success &= ParseField(field, start, end, generic_field, metadata);
+      success &= ParseField(field, start, end, table, generic_field, metadata);
     }
   } else {  // Parse all other events.
     for (const Field& field : info.fields) {
-      success &= ParseField(field, start, end, nested, metadata);
+      success &= ParseField(field, start, end, table, nested, metadata);
     }
   }
 
@@ -595,6 +668,7 @@
 bool CpuReader::ParseField(const Field& field,
                            const uint8_t* start,
                            const uint8_t* end,
+                           const ProtoTranslationTable* table,
                            protozero::Message* message,
                            FtraceMetadata* metadata) {
   PERFETTO_DCHECK(start + field.ftrace_offset + field.ftrace_size <= end);
@@ -638,10 +712,22 @@
                             field_id, message);
     case kCStringToString:
       // TODO(hjd): Kernel-dive to check this how size:0 char fields work.
-      return ReadIntoString(field_start, end, field.proto_field_id, message);
-    case kStringPtrToString:
-      // TODO(hjd): Figure out how to read these.
+      return ReadIntoString(field_start, end, field_id, message);
+    case kStringPtrToString: {
+      uint64_t n = 0;
+      // The ftrace field may be 8 or 4 bytes and we need to copy it into the
+      // bottom of n. In the unlikely case where the field is >8 bytes we
+      // should avoid making things worse by corrupting the stack but we
+      // don't need to handle it correctly.
+      size_t size = std::min<size_t>(field.ftrace_size, sizeof(n));
+      memcpy(base::AssumeLittleEndian(&n),
+             reinterpret_cast<const void*>(field_start), size);
+      // Look up the adddress in the printk format map and write it into the
+      // proto.
+      base::StringView name = table->LookupTraceString(n);
+      message->AppendBytes(field_id, name.begin(), name.size());
       return true;
+    }
     case kDataLocToString:
       return ReadDataLoc(start, field_start, end, field, message);
     case kBoolToUint32:
@@ -668,6 +754,9 @@
     case kDevId64ToUint64:
       ReadDevId<uint64_t>(field_start, field_id, message, metadata);
       return true;
+    case kFtraceSymAddr64ToUint64:
+      ReadSymbolAddr<uint64_t>(field_start, field_id, message, metadata);
+      return true;
     case kInvalidTranslationStrategy:
       break;
   }
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index f329044..ba0f9fd 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -42,6 +42,7 @@
 namespace perfetto {
 
 class FtraceDataSource;
+class LazyKernelSymbolizer;
 class ProtoTranslationTable;
 struct FtraceDataSourceConfig;
 
@@ -65,6 +66,7 @@
 
   CpuReader(size_t cpu,
             const ProtoTranslationTable* table,
+            LazyKernelSymbolizer* symbolizer,
             base::ScopedFile trace_fd);
   ~CpuReader();
 
@@ -119,6 +121,22 @@
     metadata->AddDevice(dev_id);
   }
 
+  template <typename T>
+  static void ReadSymbolAddr(const uint8_t* start,
+                             uint32_t field_id,
+                             protozero::Message* out,
+                             FtraceMetadata* metadata) {
+    // ReadSymbolAddr is a bit special. In order to not disclose KASLR layout
+    // via traces, we put in the trace only a mangled address (which really is
+    // the insertion order into metadata.kernel_addrs). We don't care about the
+    // actual symbol addesses. We just need to match that against the symbol
+    // name in the names in the FtraceEventBundle.KernelSymbols.
+    T full_addr;
+    memcpy(&full_addr, reinterpret_cast<const void*>(start), sizeof(T));
+    uint32_t interned_index = metadata->AddSymbolAddr(full_addr);
+    out->AppendVarInt(field_id, interned_index);
+  }
+
   static void ReadPid(const uint8_t* start,
                       uint32_t field_id,
                       protozero::Message* out,
@@ -190,6 +208,7 @@
   static bool ParseField(const Field& field,
                          const uint8_t* start,
                          const uint8_t* end,
+                         const ProtoTranslationTable* table,
                          protozero::Message* message,
                          FtraceMetadata* metadata);
 
@@ -219,7 +238,8 @@
                                         const FtraceDataSourceConfig* ds_config,
                                         const uint8_t* parsing_buf,
                                         const size_t pages_read,
-                                        const ProtoTranslationTable* table);
+                                        const ProtoTranslationTable* table,
+                                        LazyKernelSymbolizer* symbolizer);
 
  private:
   CpuReader(const CpuReader&) = delete;
@@ -237,6 +257,7 @@
 
   const size_t cpu_;
   const ProtoTranslationTable* const table_;
+  LazyKernelSymbolizer* const symbolizer_;
   base::ScopedFile trace_fd_;
 };
 
diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
index ee6d246..6a647a9 100644
--- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc
+++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
@@ -15,6 +15,7 @@
 #include <benchmark/benchmark.h>
 
 #include "perfetto/ext/base/utils.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_stream_null_delegate.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
@@ -310,13 +311,16 @@
 
   ScatteredStreamWriterNullDelegate delegate(perfetto::base::kPageSize);
   ScatteredStreamWriter stream(&delegate);
-  FtraceEventBundle writer;
+  protozero::RootMessage<FtraceEventBundle> writer;
 
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  FtraceDataSourceConfig ds_config{
-      EventFilter{}, DisabledCompactSchedConfigForTesting(), {}, {}};
+  FtraceDataSourceConfig ds_config{EventFilter{},
+                                   DisabledCompactSchedConfigForTesting(),
+                                   {},
+                                   {},
+                                   false /*symbolize_ksyms*/};
   ds_config.event_filter.AddEnabledEvent(
       table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
index 47c5351..db943a6 100644
--- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
+++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
@@ -53,8 +53,11 @@
   memcpy(g_page, data, std::min(base::kPageSize, size));
 
   FtraceMetadata metadata{};
-  FtraceDataSourceConfig ds_config{
-      EventFilter{}, DisabledCompactSchedConfigForTesting(), {}, {}};
+  FtraceDataSourceConfig ds_config{EventFilter{},
+                                   DisabledCompactSchedConfigForTesting(),
+                                   {},
+                                   {},
+                                   /*symbolize_ksyms=*/false};
   ds_config.event_filter.AddEnabledEvent(
       table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
   ds_config.event_filter.AddEnabledEvent(
@@ -63,7 +66,7 @@
   NullTraceWriter null_writer;
   CpuReader::ProcessPagesForDataSource(&null_writer, &metadata, /*cpu=*/0,
                                        &ds_config, g_page, /*pages_read=*/1,
-                                       table);
+                                       table, /*symbolizer*/ nullptr);
 }
 
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 2d10829..688dd7c 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -37,6 +37,7 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
 #include "protos/perfetto/trace/ftrace/sched.gen.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 #include "src/traced/probes/ftrace/test/test_messages.gen.h"
@@ -61,8 +62,11 @@
 namespace {
 
 FtraceDataSourceConfig EmptyConfig() {
-  return FtraceDataSourceConfig{
-      EventFilter{}, DisabledCompactSchedConfigForTesting(), {}, {}};
+  return FtraceDataSourceConfig{EventFilter{},
+                                DisabledCompactSchedConfigForTesting(),
+                                {},
+                                {},
+                                false /*symbolize_ksyms*/};
 }
 
 constexpr uint64_t kNanoInSecond = 1000 * 1000 * 1000;
@@ -112,24 +116,20 @@
 template <class ZeroT, class ProtoT>
 class ProtoProvider {
  public:
-  explicit ProtoProvider(size_t chunk_size)
-      : chunk_size_(chunk_size), delegate_(chunk_size_), stream_(&delegate_) {
-    delegate_.set_writer(&stream_);
-    writer_.Reset(&stream_);
-  }
+  explicit ProtoProvider(size_t chunk_size) : chunk_size_(chunk_size) {}
   ~ProtoProvider() = default;
 
-  ZeroT* writer() { return &writer_; }
+  ZeroT* writer() { return writer_.get(); }
+  void ResetWriter() { writer_.Reset(); }
 
   // Stitch together the scattered chunks into a single buffer then attempt
   // to parse the buffer as a FtraceEventBundle. Returns the FtraceEventBundle
   // on success and nullptr on failure.
   std::unique_ptr<ProtoT> ParseProto() {
     auto bundle = std::unique_ptr<ProtoT>(new ProtoT());
-    std::vector<uint8_t> buffer = delegate_.StitchSlices();
-    if (!bundle->ParseFromArray(buffer.data(), buffer.size())) {
+    std::vector<uint8_t> buffer = writer_.SerializeAsArray();
+    if (!bundle->ParseFromArray(buffer.data(), buffer.size()))
       return nullptr;
-    }
     return bundle;
   }
 
@@ -138,9 +138,7 @@
   ProtoProvider& operator=(const ProtoProvider&) = delete;
 
   size_t chunk_size_;
-  protozero::ScatteredHeapBuffer delegate_;
-  protozero::ScatteredStreamWriter stream_;
-  ZeroT writer_;
+  protozero::HeapBuffered<ZeroT> writer_;
 };
 
 using BundleProvider = ProtoProvider<protos::pbzero::FtraceEventBundle,
@@ -818,8 +816,11 @@
   ProtoTranslationTable* table = GetTable(test_case->name);
   auto page = PageFromXxd(test_case->data);
 
-  FtraceDataSourceConfig ds_config{
-      EventFilter{}, EnabledCompactSchedConfigForTesting(), {}, {}};
+  FtraceDataSourceConfig ds_config{EventFilter{},
+                                   EnabledCompactSchedConfigForTesting(),
+                                   {},
+                                   {},
+                                   false /* symbolize_ksyms*/};
   ds_config.event_filter.AddEnabledEvent(
       table->EventToFtraceId(GroupAndName("sched", "sched_switch")));
 
@@ -846,6 +847,7 @@
   ASSERT_TRUE(bundle);
   EXPECT_EQ(0u, bundle->event().size());
   EXPECT_FALSE(bundle->has_compact_sched());
+  bundle_provider.ResetWriter();
 
   // Instead, sched switch fields were buffered:
   EXPECT_LT(0u, compact_buffer.sched_switch().size());
@@ -996,10 +998,21 @@
     }
 
     {
+      // char* -> string
+      event->fields.emplace_back(Field{});
+      Field* field = &event->fields.back();
+      field->ftrace_offset = 56;
+      field->ftrace_size = 8;
+      field->ftrace_type = kFtraceStringPtr;
+      field->proto_field_id = 503;
+      field->proto_field_type = ProtoSchemaType::kString;
+    }
+
+    {
       // dataloc -> string
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
-      field->ftrace_offset = 57;
+      field->ftrace_offset = 65;
       field->ftrace_size = 4;
       field->ftrace_type = kFtraceDataLoc;
       field->proto_field_id = 502;
@@ -1010,7 +1023,7 @@
       // char -> string
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
-      field->ftrace_offset = 61;
+      field->ftrace_offset = 69;
       field->ftrace_size = 0;
       field->ftrace_type = kFtraceCString;
       field->proto_field_id = 501;
@@ -1023,10 +1036,12 @@
     }
   }
 
+  PrintkMap printk_formats;
+  printk_formats.insert(0xffffff8504f51b23, "my_printk_format_string");
   ProtoTranslationTable table(
       &ftrace_, events, std::move(common_fields),
       ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-      InvalidCompactSchedEventFormatForTesting());
+      InvalidCompactSchedEventFormatForTesting(), printk_formats);
 
   FakeEventProvider provider(base::kPageSize);
 
@@ -1053,6 +1068,7 @@
   writer.Write<int64_t>(k64BitKernelBlockDeviceId);  // Dev id 64
   writer.Write<int64_t>(99u);                        // Inode 64
   writer.WriteFixedString(16, "Hello");
+  writer.Write<uint64_t>(0xffffff8504f51b23ULL);  // char* (printk formats)
   writer.Write<uint8_t>(0);  // Deliberately mis-aligning.
   writer.Write<uint32_t>(40 | 6 << 16);
   writer.WriteFixedString(300, "Goodbye");
@@ -1076,13 +1092,14 @@
             static_cast<uint32_t>(kUserspaceBlockDeviceId));
   EXPECT_EQ(event->all_fields().field_inode_32(), 98u);
 // TODO(primiano): for some reason this fails on mac.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   EXPECT_EQ(event->all_fields().field_dev_64(), k64BitUserspaceBlockDeviceId);
 #endif
   EXPECT_EQ(event->all_fields().field_inode_64(), 99u);
   EXPECT_EQ(event->all_fields().field_char_16(), "Hello");
   EXPECT_EQ(event->all_fields().field_char(), "Goodbye");
   EXPECT_EQ(event->all_fields().field_data_loc(), "Hello");
+  EXPECT_EQ(event->all_fields().field_char_star(), "my_printk_format_string");
   EXPECT_THAT(metadata.pids, Contains(97));
   EXPECT_EQ(metadata.inode_and_device.size(), 2U);
   EXPECT_THAT(metadata.inode_and_device,
@@ -1165,7 +1182,8 @@
 
   TraceWriterForTesting trace_writer;
   CpuReader::ProcessPagesForDataSource(&trace_writer, &metadata, /*cpu=*/1,
-                                       &ds_config, buf, kTestPages, table);
+                                       &ds_config, buf, kTestPages, table,
+                                       /*symbolizer=*/nullptr);
 
   // Each packet should contain the parsed contents of a contiguous run of pages
   // without data loss.
@@ -1646,6 +1664,95 @@
 // clang-format off
 // # tracer: nop
 // #
+// # entries-in-buffer/entries-written: 18/18   #P:8
+// #
+// #                              _-----=> irqs-off
+// #                             / _----=> need-resched
+// #                            | / _---=> hardirq/softirq
+// #                            || / _--=> preempt-depth
+// #                            ||| /     delay
+// #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
+// #              | |       |   ||||       |         |
+//            <...>-9290  [000] ....  1352.654573: suspend_resume: sync_filesystems[0] end
+//            <...>-9290  [000] ....  1352.665366: suspend_resume: freeze_processes[0] begin
+//            <...>-9290  [000] ....  1352.699711: suspend_resume: freeze_processes[0] end
+//            <...>-9290  [000] ....  1352.699718: suspend_resume: suspend_enter[1] end
+//            <...>-9290  [000] ....  1352.699723: suspend_resume: dpm_prepare[2] begin
+//            <...>-9290  [000] ....  1352.703470: suspend_resume: dpm_prepare[2] end
+//            <...>-9290  [000] ....  1352.703477: suspend_resume: dpm_suspend[2] begin
+//            <...>-9290  [000] ....  1352.720107: suspend_resume: dpm_resume[16] end
+//            <...>-9290  [000] ....  1352.720113: suspend_resume: dpm_complete[16] begin
+//            <...>-9290  [000] .n..  1352.724540: suspend_resume: dpm_complete[16] end
+//            <...>-9290  [000] ....  1352.724567: suspend_resume: resume_console[1] begin
+//            <...>-9290  [000] ....  1352.724570: suspend_resume: resume_console[1] end
+//            <...>-9290  [000] ....  1352.724574: suspend_resume: thaw_processes[0] begin
+static ExamplePage g_suspend_resume {
+    "synthetic",
+    R"(00000000: edba 155a 3201 0000 7401 0000 0000 0000  ...Z2...t.......
+00000010: 7e58 22cd 1201 0000 0600 0000 ac00 0000  ~X".............
+00000020: 4a24 0000 5a7a f504 85ff ffff 0000 0000  J$..Zz..........
+00000030: 0017 0000 c621 9614 ac00 0000 4a24 0000  .....!......J$..
+00000040: 1c7a f504 85ff ffff 0000 0000 0100 0000  .z..............
+00000050: e6f1 8141 ac00 0000 4a24 0000 1c7a f504  ...A....J$...z..
+00000060: 85ff ffff 0000 0000 0000 0000 8682 0300  ................
+00000070: ac00 0000 4a24 0000 4c7a f504 85ff ffff  ....J$..Lz......
+00000080: 0100 0000 0063 755f 0657 0200 ac00 0000  .....cu_.W......
+00000090: 4a24 0000 8ad5 0105 85ff ffff 0200 0000  J$..............
+000000a0: 0100 0000 06b5 2507 ac00 0000 4a24 0000  ......%.....J$..
+000000b0: 8ad5 0105 85ff ffff 0200 0000 0000 0000  ................
+000000c0: 460d 0300 ac00 0000 4a24 0000 51d5 0105  F.......J$..Q...
+000000d0: 85ff ffff 0200 0000 0117 0000 c63e b81f  .............>..
+000000e0: ac00 0000 4a24 0000 7fd5 0105 85ff ffff  ....J$..........
+000000f0: 1000 0000 0010 0b00 a6f9 0200 ac00 0000  ................
+00000100: 4a24 0000 96d5 0105 85ff ffff 1000 0000  J$..............
+00000110: 01c0 1f00 a6dd 7108 ac00 0400 4a24 0000  ......q.....J$..
+00000120: 96d5 0105 85ff ffff 1000 0000 0000 0000  ................
+00000130: c6f1 0c00 ac00 0000 4a24 0000 3d7a f504  ........J$..=z..
+00000140: 85ff ffff 0100 0000 01ea 24d5 a66c 0100  ..........$..l..
+00000150: ac00 0000 4a24 0000 3d7a f504 85ff ffff  ....J$..=z......
+00000160: 0100 0000 0000 0001 6636 0200 ac00 0000  ........f6......
+00000170: 4a24 0000 d178 f504 85ff ffff 0000 0000  J$...x..........
+00000180: 0100 0000 0000 0000 0000 0000 0000 0000  ................
+00000190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
+)"};
+
+TEST(CpuReaderTest, ParseSuspendResume) {
+  const ExamplePage* test_case = &g_suspend_resume;
+
+  BundleProvider bundle_provider(base::kPageSize);
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  ds_config.event_filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("power", "suspend_resume")));
+
+  FtraceMetadata metadata{};
+  CompactSchedBuffer compact_buffer;
+  const uint8_t* parse_pos = page.get();
+  base::Optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+  ASSERT_TRUE(page_header.has_value());
+
+  CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config, &compact_buffer,
+      bundle_provider.writer(), &metadata);
+  auto bundle = bundle_provider.ParseProto();
+  ASSERT_TRUE(bundle);
+  ASSERT_EQ(bundle->event().size(), 13u);
+  EXPECT_EQ(bundle->event()[0].suspend_resume().action(), "sync_filesystems");
+  EXPECT_EQ(bundle->event()[1].suspend_resume().action(), "freeze_processes");
+  EXPECT_EQ(bundle->event()[2].suspend_resume().action(), "freeze_processes");
+  EXPECT_EQ(bundle->event()[3].suspend_resume().action(), "suspend_enter");
+  // dpm_prepare deliberately missing from:
+  // src/traced/probes/ftrace/test/data/synthetic/printk_formats to ensure we
+  // handle that case correctly.
+  EXPECT_EQ(bundle->event()[4].suspend_resume().action(), "");
+}
+
+// clang-format off
+// # tracer: nop
+// #
 // # entries-in-buffer/entries-written: 1041/238740   #P:8
 // #
 // #                              _-----=> irqs-off
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index a90eca9..b49ac87 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -1009,6 +1009,107 @@
        kUnsetFtraceId,
        112,
        kUnsetSize},
+      {"cpuhp_exit",
+       "cpuhp",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cpu", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "idx", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ret", 3, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "state", 4, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       343,
+       kUnsetSize},
+      {"cpuhp_multi_enter",
+       "cpuhp",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cpu", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "fun", 2, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "idx", 3, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "target", 4, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       344,
+       kUnsetSize},
+      {"cpuhp_enter",
+       "cpuhp",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cpu", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "fun", 2, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "idx", 3, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "target", 4, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       345,
+       kUnsetSize},
+      {"cpuhp_latency",
+       "cpuhp",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cpu", 1, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "ret", 2, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "state", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "time", 4, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       346,
+       kUnsetSize},
+      {"tracing_mark_write",
+       "dpu",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "pid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "trace_name", 2, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "trace_begin", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "name", 4, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "type", 5, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "value", 6, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       348,
+       kUnsetSize},
       {"ext4_da_write_begin",
        "ext4",
        {
@@ -3823,6 +3924,22 @@
        kUnsetFtraceId,
        273,
        kUnsetSize},
+      {"fastrpc_dma_stat",
+       "fastrpc",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "cid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "len", 2, ProtoSchemaType::kInt64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "total_allocated", 3, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       347,
+       kUnsetSize},
       {"fence_init",
        "fence",
        {
@@ -3953,6 +4070,31 @@
        kUnsetFtraceId,
        3,
        kUnsetSize},
+      {"tracing_mark_write",
+       "g2d",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "pid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "trace_name", 2, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "trace_begin", 3, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "name", 4, ProtoSchemaType::kString,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "type", 5, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "value", 6, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       349,
+       kUnsetSize},
       {"gpu_mem_total",
        "gpu_mem",
        {
diff --git a/src/traced/probes/ftrace/event_info_constants.cc b/src/traced/probes/ftrace/event_info_constants.cc
index 5c020d9..a3e59d2 100644
--- a/src/traced/probes/ftrace/event_info_constants.cc
+++ b/src/traced/probes/ftrace/event_info_constants.cc
@@ -99,6 +99,8 @@
     *out = kBoolToUint64;
   } else if (ftrace == kFtraceDataLoc && proto == ProtoSchemaType::kString) {
     *out = kDataLocToString;
+  } else if (ftrace == kFtraceSymAddr64 && proto == ProtoSchemaType::kUint64) {
+    *out = kFtraceSymAddr64ToUint64;
   } else {
     PERFETTO_DLOG("No translation strategy for '%s' -> '%s'", ToString(ftrace),
                   ProtoSchemaToString(proto));
diff --git a/src/traced/probes/ftrace/event_info_constants.h b/src/traced/probes/ftrace/event_info_constants.h
index 923ee62..0283f12 100644
--- a/src/traced/probes/ftrace/event_info_constants.h
+++ b/src/traced/probes/ftrace/event_info_constants.h
@@ -48,6 +48,7 @@
   kFtraceDevId32,
   kFtraceDevId64,
   kFtraceDataLoc,
+  kFtraceSymAddr64,
 };
 
 // Joint enum of FtraceFieldType (left) and ProtoFieldType (right).
@@ -83,6 +84,7 @@
   kDevId32ToUint64,
   kDevId64ToUint64,
   kDataLocToString,
+  kFtraceSymAddr64ToUint64,
 };
 
 inline const char* ToString(FtraceFieldType v) {
@@ -125,6 +127,8 @@
       return "devid64";
     case kFtraceDataLoc:
       return "__data_loc";
+    case kFtraceSymAddr64:
+      return "void*";
     case kInvalidFtraceFieldType:
       break;
   }
diff --git a/src/traced/probes/ftrace/event_info_unittest.cc b/src/traced/probes/ftrace/event_info_unittest.cc
index 6c3f9d8..5fbee6d 100644
--- a/src/traced/probes/ftrace/event_info_unittest.cc
+++ b/src/traced/probes/ftrace/event_info_unittest.cc
@@ -23,7 +23,7 @@
 namespace {
 using protozero::proto_utils::ProtoSchemaType;
 
-TEST(EventInfoTest, GetStaticEventInfoSanityCheck) {
+TEST(EventInfoTest, GetStaticEventInfoValidations) {
   std::vector<Event> events = GetStaticEventInfo();
   for (const Event& event : events) {
     // For each event the following fields should be filled
@@ -53,7 +53,7 @@
   }
 }
 
-TEST(EventInfoTest, GetStaticCommonFieldsInfoSanityCheck) {
+TEST(EventInfoTest, GetStaticCommonFieldsInfoValidations) {
   std::vector<Field> fields = GetStaticCommonFieldsInfo();
   for (const Field& field : fields) {
     // Non-empty name, group, and proto field id.
@@ -69,7 +69,7 @@
   }
 }
 
-TEST(EventInfoTest, SetTranslationStrategySanityCheck) {
+TEST(EventInfoTest, SetTranslationStrategyValidations) {
   TranslationStrategy strategy = kUint32ToUint32;
   ASSERT_FALSE(SetTranslationStrategy(kFtraceCString, ProtoSchemaType::kUint64,
                                       &strategy));
diff --git a/src/traced/probes/ftrace/format_parser/BUILD.gn b/src/traced/probes/ftrace/format_parser/BUILD.gn
new file mode 100644
index 0000000..e9aa26e
--- /dev/null
+++ b/src/traced/probes/ftrace/format_parser/BUILD.gn
@@ -0,0 +1,37 @@
+# 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.
+
+import("../../../../../gn/perfetto.gni")
+import("../../../../../gn/test.gni")
+
+source_set("format_parser") {
+  deps = [
+    "../../../../../gn:default_deps",
+    "../../../../base",
+  ]
+  sources = [
+    "format_parser.cc",
+    "format_parser.h",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    ":format_parser",
+    "../../../../../gn:default_deps",
+    "../../../../../gn:gtest_and_gmock",
+  ]
+  sources = [ "format_parser_unittest.cc" ]
+}
diff --git a/src/traced/probes/ftrace/format_parser.cc b/src/traced/probes/ftrace/format_parser/format_parser.cc
similarity index 98%
rename from src/traced/probes/ftrace/format_parser.cc
rename to src/traced/probes/ftrace/format_parser/format_parser.cc
index 2be687d..d9c12e6 100644
--- a/src/traced/probes/ftrace/format_parser.cc
+++ b/src/traced/probes/ftrace/format_parser/format_parser.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
 
 #include <string.h>
 
diff --git a/src/traced/probes/ftrace/format_parser.h b/src/traced/probes/ftrace/format_parser/format_parser.h
similarity index 94%
rename from src/traced/probes/ftrace/format_parser.h
rename to src/traced/probes/ftrace/format_parser/format_parser.h
index 65716f4..a8a5cdb 100644
--- a/src/traced/probes/ftrace/format_parser.h
+++ b/src/traced/probes/ftrace/format_parser/format_parser.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_H_
-#define SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_H_
+#ifndef SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_FORMAT_PARSER_H_
+#define SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_FORMAT_PARSER_H_
 
 #include <stdint.h>
 #include <string>
@@ -92,4 +92,4 @@
 
 }  // namespace perfetto
 
-#endif  // SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_H_
+#endif  // SRC_TRACED_PROBES_FTRACE_FORMAT_PARSER_FORMAT_PARSER_H_
diff --git a/src/traced/probes/ftrace/format_parser_unittest.cc b/src/traced/probes/ftrace/format_parser/format_parser_unittest.cc
similarity index 98%
rename from src/traced/probes/ftrace/format_parser_unittest.cc
rename to src/traced/probes/ftrace/format_parser/format_parser_unittest.cc
index 3e92f67..f4e9523 100644
--- a/src/traced/probes/ftrace/format_parser_unittest.cc
+++ b/src/traced/probes/ftrace/format_parser/format_parser_unittest.cc
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
 
 #include "test/gtest_and_gmock.h"
 
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 515a9da..56f61f0 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -164,6 +164,13 @@
         events.insert(GroupAndName("sde", "sde_evtlog"));
         events.insert(GroupAndName("sde", "sde_encoder_underrun"));
         events.insert(GroupAndName("sde", "sde_cmd_release_bw"));
+
+        AddEventGroup(table, "dpu", &events);
+        events.insert(GroupAndName("dpu", "tracing_mark_write"));
+
+        AddEventGroup(table, "g2d", &events);
+        events.insert(GroupAndName("g2d", "tracing_mark_write"));
+        events.insert(GroupAndName("g2d", "g2d_perf_update_qos"));
         continue;
       }
 
@@ -508,12 +515,11 @@
 
   std::vector<std::string> apps(request.atrace_apps());
   std::vector<std::string> categories(request.atrace_categories());
-
   FtraceConfigId id = ++last_id_;
   ds_configs_.emplace(
       std::piecewise_construct, std::forward_as_tuple(id),
       std::forward_as_tuple(std::move(filter), compact_sched, std::move(apps),
-                            std::move(categories)));
+                            std::move(categories), request.symbolize_ksyms()));
   return id;
 }
 
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.h b/src/traced/probes/ftrace/ftrace_config_muxer.h
index dc758ea..3965371 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.h
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.h
@@ -34,11 +34,13 @@
   FtraceDataSourceConfig(EventFilter _event_filter,
                          CompactSchedConfig _compact_sched,
                          std::vector<std::string> _atrace_apps,
-                         std::vector<std::string> _atrace_categories)
+                         std::vector<std::string> _atrace_categories,
+                         bool _symbolize_ksyms)
       : event_filter(std::move(_event_filter)),
         compact_sched(_compact_sched),
         atrace_apps(std::move(_atrace_apps)),
-        atrace_categories(std::move(_atrace_categories)) {}
+        atrace_categories(std::move(_atrace_categories)),
+        symbolize_ksyms(_symbolize_ksyms) {}
 
   // The event filter allows to quickly check if a certain ftrace event with id
   // x is enabled for this data source.
@@ -50,6 +52,9 @@
   // Used only in Android for ATRACE_EVENT/os.Trace() userspace annotations.
   std::vector<std::string> atrace_apps;
   std::vector<std::string> atrace_categories;
+
+  // When enabled will turn on the the kallsyms symbolizer in CpuReader.
+  const bool symbolize_ksyms;
 };
 
 // Ftrace is a bunch of globally modifiable persistent state.
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index d9b5911..c3602aa 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -84,7 +84,8 @@
                               events,
                               common_fields,
                               ftrace_page_header_spec,
-                              compact_sched_format) {}
+                              compact_sched_format,
+                              PrintkMap()) {}
   MOCK_METHOD1(GetOrCreateEvent, Event*(const GroupAndName& group_and_name));
   MOCK_CONST_METHOD1(GetEvent,
                      const Event*(const GroupAndName& group_and_name));
@@ -170,7 +171,7 @@
     return std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
         &table_procfs_, events, std::move(common_fields),
         ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-        compact_format));
+        compact_format, PrintkMap()));
   }
 
   NiceMock<MockFtraceProcfs> table_procfs_;
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index da88e3d..7ab52e7 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -34,6 +34,8 @@
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
+#include "src/kallsyms/kernel_symbol_map.h"
+#include "src/kallsyms/lazy_kernel_symbolizer.h"
 #include "src/traced/probes/ftrace/atrace_hal_wrapper.h"
 #include "src/traced/probes/ftrace/cpu_reader.h"
 #include "src/traced/probes/ftrace/cpu_stats_parser.h"
@@ -96,11 +98,9 @@
 }  // namespace
 
 const char* const FtraceController::kTracingPaths[] = {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-    "/sys/kernel/tracing/", "/sys/kernel/debug/tracing/", nullptr,
-#else
-    "/sys/kernel/debug/tracing/", nullptr,
-#endif
+    "/sys/kernel/tracing/",
+    "/sys/kernel/debug/tracing/",
+    nullptr,
 };
 
 // Method of last resort to reset ftrace state.
@@ -158,6 +158,7 @@
                                    Observer* observer)
     : task_runner_(task_runner),
       observer_(observer),
+      symbolizer_(new LazyKernelSymbolizer()),
       ftrace_procfs_(std::move(ftrace_procfs)),
       table_(std::move(table)),
       ftrace_config_muxer_(std::move(model)),
@@ -192,7 +193,8 @@
   size_t period_page_quota = ftrace_config_muxer_->GetPerCpuBufferSizePages();
   for (size_t cpu = 0; cpu < ftrace_procfs_->NumberOfCpus(); cpu++) {
     auto reader = std::unique_ptr<CpuReader>(
-        new CpuReader(cpu, table_.get(), ftrace_procfs_->OpenPipeForCpu(cpu)));
+        new CpuReader(cpu, table_.get(), symbolizer_.get(),
+                      ftrace_procfs_->OpenPipeForCpu(cpu)));
     per_cpu_.emplace_back(std::move(reader), period_page_quota);
   }
 
@@ -238,6 +240,16 @@
     return;
   }
 
+#if PERFETTO_DCHECK_IS_ON()
+  // The OnFtraceDataWrittenIntoDataSourceBuffers() below is supposed to clear
+  // all metadata, including the |kernel_addrs| map for symbolization.
+  for (FtraceDataSource* ds : started_data_sources_) {
+    FtraceMetadata* ftrace_metadata = ds->mutable_metadata();
+    PERFETTO_DCHECK(ftrace_metadata->kernel_addrs.empty());
+    PERFETTO_DCHECK(ftrace_metadata->last_kernel_addr_index_written == 0);
+  }
+#endif
+
   // Read all cpu buffers with remaining per-period quota.
   bool all_cpus_done = true;
   uint8_t* parsing_buf = reinterpret_cast<uint8_t*>(parsing_mem_.Get());
@@ -343,6 +355,7 @@
   // non-graceful stop.
 
   per_cpu_.clear();
+  symbolizer_->Destroy();
 
   if (parsing_mem_.IsValid()) {
     parsing_mem_.AdviseDontNeed(parsing_mem_.Get(), parsing_mem_.size());
@@ -376,6 +389,23 @@
 
   started_data_sources_.insert(data_source);
   StartIfNeeded();
+
+  // If the config is requesting to symbolize kernel addresses, create the
+  // symbolizer and parse /proc/kallsyms (it will take 200-300 ms). This is not
+  // strictly required here but is to avoid hitting the parsing cost while
+  // processing the first ftrace event batch in CpuReader.
+  if (data_source->config().symbolize_ksyms()) {
+    if (data_source->config().initialize_ksyms_synchronously_for_testing()) {
+      symbolizer_->GetOrCreateKernelSymbolMap();
+    } else {
+      auto weak_this = weak_factory_.GetWeakPtr();
+      task_runner_->PostTask([weak_this] {
+        if (weak_this)
+          weak_this->symbolizer_->GetOrCreateKernelSymbolMap();
+      });
+    }
+  }
+
   return true;
 }
 
@@ -390,6 +420,13 @@
 
 void FtraceController::DumpFtraceStats(FtraceStats* stats) {
   DumpAllCpuStats(ftrace_procfs_.get(), stats);
+  if (symbolizer_ && symbolizer_->is_valid()) {
+    auto* symbol_map = symbolizer_->GetOrCreateKernelSymbolMap();
+    stats->kernel_symbols_parsed =
+        static_cast<uint32_t>(symbol_map->num_syms());
+    stats->kernel_symbols_mem_kb =
+        static_cast<uint32_t>(symbol_map->size_bytes() / 1024);
+  }
 }
 
 FtraceController::Observer::~Observer() = default;
diff --git a/src/traced/probes/ftrace/ftrace_controller.h b/src/traced/probes/ftrace/ftrace_controller.h
index 0004a91..1186619 100644
--- a/src/traced/probes/ftrace/ftrace_controller.h
+++ b/src/traced/probes/ftrace/ftrace_controller.h
@@ -40,6 +40,7 @@
 class FtraceConfigMuxer;
 class FtraceDataSource;
 class FtraceProcfs;
+class LazyKernelSymbolizer;
 class ProtoTranslationTable;
 struct FtraceStats;
 
@@ -114,6 +115,7 @@
   base::TaskRunner* const task_runner_;
   Observer* const observer_;
   base::PagedMemory parsing_mem_;
+  std::unique_ptr<LazyKernelSymbolizer> symbolizer_;
   std::unique_ptr<FtraceProcfs> ftrace_procfs_;
   std::unique_ptr<ProtoTranslationTable> table_;
   std::unique_ptr<FtraceConfigMuxer> ftrace_config_muxer_;
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index dca24c8..a3a02cc 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -20,6 +20,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include "perfetto/ext/base/file_utils.h"
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/cpu_reader.h"
 #include "src/traced/probes/ftrace/ftrace_config_muxer.h"
@@ -87,7 +88,7 @@
   return std::unique_ptr<Table>(
       new Table(ftrace, events, std::move(common_fields),
                 ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-                InvalidCompactSchedEventFormatForTesting()));
+                InvalidCompactSchedEventFormatForTesting(), PrintkMap()));
 }
 
 std::unique_ptr<FtraceConfigMuxer> FakeModel(FtraceProcfs* ftrace,
diff --git a/src/traced/probes/ftrace/ftrace_metadata.h b/src/traced/probes/ftrace/ftrace_metadata.h
index 8294f25..cdfc138 100644
--- a/src/traced/probes/ftrace/ftrace_metadata.h
+++ b/src/traced/probes/ftrace/ftrace_metadata.h
@@ -35,6 +35,23 @@
 // Container for tracking miscellaneous information while parsing ftrace events,
 // scoped to an individual data source.
 struct FtraceMetadata {
+  struct KernelAddr {
+    KernelAddr(uint64_t _addr, uint32_t _index) : addr(_addr), index(_index) {}
+    uint64_t addr = 0;
+    uint32_t index = 0;
+
+    // We never keep more than one KernelAddr entry per address in the set. This
+    // is really just a workaround for the lack of a FlatMap.
+    // The |index| is written only after the entry is added to the set, to have
+    // a monotonic value that reflects the insertion order.
+    friend bool operator<(const KernelAddr& lhs, const KernelAddr& rhs) {
+      return lhs.addr < rhs.addr;
+    }
+    friend bool operator==(const KernelAddr& lhs, const KernelAddr& rhs) {
+      return lhs.addr == rhs.addr;
+    }
+  };
+
   FtraceMetadata() {
     // A sched_switch is 64 bytes, a page is 4096 bytes and we expect
     // 2 pid's per sched_switch. 4096/64*2=128. Give it a 2x margin.
@@ -42,6 +59,8 @@
 
     // We expect to see only a small number of task rename events.
     rename_pids.reserve(32);
+
+    kernel_addrs.reserve(256);
   }
 
   void AddDevice(BlockDeviceID device_id) {
@@ -85,11 +104,26 @@
     AddPid(pid);
   }
 
+  // Returns the index of the symbol (a monotonic counter, which is set when
+  // the symbol is inserted the first time).
+  uint32_t AddSymbolAddr(uint64_t addr) {
+    auto it_and_inserted = kernel_addrs.insert(KernelAddr(addr, 0));
+    // Deliberately prefer a branch here to always computing and passing
+    // size + 1 to the above.
+    if (it_and_inserted.second) {
+      const auto index = static_cast<uint32_t>(kernel_addrs.size());
+      it_and_inserted.first->index = index;
+    }
+    return it_and_inserted.first->index;
+  }
+
   void Clear() {
     inode_and_device.clear();
     rename_pids.clear();
     pids.clear();
     pids_cache.reset();
+    kernel_addrs.clear();
+    last_kernel_addr_index_written = 0;
     FinishEvent();
   }
 
@@ -106,10 +140,12 @@
   bool seen_device_id = false;
 #endif
   int32_t last_seen_common_pid = 0;
+  uint32_t last_kernel_addr_index_written = 0;
 
   base::FlatSet<InodeBlockPair> inode_and_device;
   base::FlatSet<int32_t> rename_pids;
   base::FlatSet<int32_t> pids;
+  base::FlatSet<KernelAddr> kernel_addrs;
 
   // This bitmap is a cache for |pids|. It speculates on the fact that on most
   // Android kernels, PID_MAX=32768. It saves ~1-2% cpu time on high load
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 480d303..5628cb7 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -109,6 +109,11 @@
   return ReadFileIntoString(path);
 }
 
+std::string FtraceProcfs::ReadPrintkFormats() const {
+  std::string path = root_ + "printk_formats";
+  return ReadFileIntoString(path);
+}
+
 std::vector<std::string> FtraceProcfs::ReadEnabledEvents() {
   std::string path = root_ + "set_event";
   std::string s = ReadFileIntoString(path);
@@ -116,7 +121,7 @@
   std::vector<std::string> events;
   while (ss.Next()) {
     std::string event = ss.cur_token();
-    if (event.size() == 0)
+    if (event.empty())
       continue;
     events.push_back(base::StripChars(event, ":", '/'));
   }
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 3d8186f..f2ea712 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -50,6 +50,9 @@
 
   virtual std::string ReadPageHeaderFormat() const;
 
+  // Read the printk formats file.
+  std::string ReadPrintkFormats() const;
+
   // Read the "/per_cpu/cpuXX/stats" file for the given |cpu|.
   std::string ReadCpuStats(size_t cpu) const;
 
diff --git a/src/traced/probes/ftrace/ftrace_stats.cc b/src/traced/probes/ftrace/ftrace_stats.cc
index eb3a99f..a424cc9 100644
--- a/src/traced/probes/ftrace/ftrace_stats.cc
+++ b/src/traced/probes/ftrace/ftrace_stats.cc
@@ -24,6 +24,8 @@
   for (const FtraceCpuStats& cpu_specific_stats : cpu_stats) {
     cpu_specific_stats.Write(writer->add_cpu_stats());
   }
+  writer->set_kernel_symbols_parsed(kernel_symbols_parsed);
+  writer->set_kernel_symbols_mem_kb(kernel_symbols_mem_kb);
 }
 
 void FtraceCpuStats::Write(protos::pbzero::FtraceCpuStats* writer) const {
diff --git a/src/traced/probes/ftrace/ftrace_stats.h b/src/traced/probes/ftrace/ftrace_stats.h
index f7e6b94..1dbcbe9 100644
--- a/src/traced/probes/ftrace/ftrace_stats.h
+++ b/src/traced/probes/ftrace/ftrace_stats.h
@@ -46,6 +46,8 @@
 
 struct FtraceStats {
   std::vector<FtraceCpuStats> cpu_stats;
+  uint32_t kernel_symbols_parsed = 0;
+  uint32_t kernel_symbols_mem_kb = 0;
 
   void Write(protos::pbzero::FtraceStats*) const;
 };
diff --git a/src/traced/probes/ftrace/printk_formats_parser.cc b/src/traced/probes/ftrace/printk_formats_parser.cc
new file mode 100644
index 0000000..274e981
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/ftrace/printk_formats_parser.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
+
+namespace perfetto {
+
+PrintkMap ParsePrintkFormats(const std::string& format) {
+  PrintkMap mapping;
+  for (base::StringSplitter lines(format, '\n'); lines.Next();) {
+    // Lines have the format:
+    // 0xdeadbeef : "not alive cow"
+    // and may be duplicated.
+    std::string line(lines.cur_token());
+
+    auto index = line.find(':');
+    if (index == std::string::npos)
+      continue;
+    std::string raw_address = line.substr(0, index);
+    std::string name = line.substr(index);
+
+    // Remove colon, space and surrounding quotes:
+    raw_address = base::StripSuffix(raw_address, " ");
+    name = base::StripPrefix(name, ":");
+    name = base::StripPrefix(name, " ");
+    name = base::StripPrefix(name, "\"");
+    name = base::StripSuffix(name, "\"");
+
+    if (name.empty())
+      continue;
+
+    base::Optional<uint64_t> address = base::StringToUInt64(raw_address, 16);
+    if (address && address.value() != 0)
+      mapping.insert(address.value(), name);
+  }
+  return mapping;
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/printk_formats_parser.h b/src/traced/probes/ftrace/printk_formats_parser.h
new file mode 100644
index 0000000..df7e556
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser.h
@@ -0,0 +1,70 @@
+/*
+ * 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_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
+#define SRC_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
+
+#include <string>
+
+#include "perfetto/base/flat_set.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+
+struct PrintkEntry {
+  uint64_t address;
+  std::string name;
+
+  PrintkEntry(uint64_t _address) : PrintkEntry(_address, "") {}
+
+  PrintkEntry(uint64_t _address, std::string _name)
+      : address(_address), name(_name) {}
+
+  bool operator<(const PrintkEntry& other) const {
+    return address < other.address;
+  }
+
+  bool operator==(const PrintkEntry& other) const {
+    return address == other.address;
+  }
+};
+
+class PrintkMap {
+ public:
+  void insert(uint64_t address, std::string name) {
+    set_.insert(PrintkEntry(address, name));
+  }
+
+  base::StringView at(uint64_t address) const {
+    auto it = set_.find(address);
+    if (it == set_.end()) {
+      return base::StringView();
+    }
+    return base::StringView(it->name);
+  }
+
+  size_t size() const { return set_.size(); }
+
+  size_t empty() const { return set_.empty(); }
+
+  base::FlatSet<PrintkEntry> set_;
+};
+
+PrintkMap ParsePrintkFormats(const std::string& format);
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
diff --git a/src/traced/probes/ftrace/printk_formats_parser_unittest.cc b/src/traced/probes/ftrace/printk_formats_parser_unittest.cc
new file mode 100644
index 0000000..59d282d
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser_unittest.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/ftrace/printk_formats_parser.h"
+
+#include "test/gtest_and_gmock.h"
+
+using ::testing::Contains;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::Key;
+using ::testing::Not;
+using ::testing::Pair;
+
+namespace perfetto {
+namespace {
+
+TEST(PrintkFormatParserTest, AllZeros) {
+  std::string format = R"(0x0 : "Rescheduling interrupts"
+0x0 : "Function call interrupts"
+0x0 : "CPU stop interrupts"
+0x0 : "Timer broadcast interrupts"
+0x0 : "IRQ work interrupts"
+0x0 : "CPU wakeup interrupts"
+0x0 : "CPU backtrace"
+0x0 : "rcu_sched"
+0x0 : "rcu_bh"
+0x0 : "rcu_preempt"
+)";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result, IsEmpty());
+}
+
+TEST(PrintkFormatParserTest, VariousAddresses) {
+  std::string format = R"(0x1 : "First line"
+0x1 : "First line"
+0x2 : "Unfortunate: colon"
+0x3 : ""
+0xffffff92349439b8 : "Large address"
+0x9 : "Last line")";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result.at(1), Eq("First line"));
+  EXPECT_THAT(result.at(2), Eq("Unfortunate: colon"));
+  EXPECT_THAT(result.at(18446743602145278392ULL), Eq("Large address"));
+  EXPECT_THAT(result.at(9), Eq("Last line"));
+  EXPECT_THAT(result.at(3), Eq(""));
+}
+
+TEST(PrintkFormatParserTest, RobustToRubbish) {
+  std::string format = R"(
+: leading colon
+trailing colon:
+multiple colons: : : : :
+Empty line:
+
+Just colon:
+:
+: "No address"
+No name:
+0x1 :
+0xbadhexaddress : "Bad hex address"
+0x2 : No quotes
+0x3:"No gap"
+"Wrong way round" : 0x4
+)";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result.at(2), Eq("No quotes"));
+  EXPECT_THAT(result.at(3), Eq("No gap"));
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 44fe5f5..9a69745 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -239,6 +239,7 @@
     case kFtraceUint64:
     case kFtraceInode32:
     case kFtraceInode64:
+    case kFtraceSymAddr64:
       *proto_type = ProtoSchemaType::kUint64;
       *proto_field_id = GenericFtraceEvent::Field::kUintValueFieldNumber;
       break;
@@ -287,6 +288,15 @@
     return true;
   }
 
+  // Kernel addresses that need symbolization via kallsyms. Only 64-bit kernels
+  // are supported for now. 32-bit kernels seems to be going away.
+  if ((base::StartsWith(type_and_name, "void*") ||
+       base::StartsWith(type_and_name, "void *")) &&
+      size == 8) {
+    *out = kFtraceSymAddr64;
+    return true;
+  }
+
   // Variable length strings: "char foo" + size: 0 (as in 'print').
   if (base::StartsWith(type_and_name, "char ") && size == 0) {
     *out = kFtraceCString;
@@ -414,7 +424,7 @@
     if (contents.empty() || !ParseFtraceEvent(contents, &ftrace_event)) {
       if (!strcmp(event.group, "ftrace") && !strcmp(event.name, "print")) {
         // On some "user" builds of Android <P the ftrace/print event is not
-        // selinux-whitelisted. Thankfully this event is an always-on built-in
+        // selinux-allowed. Thankfully this event is an always-on built-in
         // so we don't need to write to its 'enable' file. However we need to
         // know its binary layout to decode it, so we hardcode it.
         ftrace_event.id = 5;  // Seems quite stable across kernels.
@@ -453,9 +463,12 @@
   // about their format hold for this kernel.
   CompactSchedEventFormat compact_sched = ValidateFormatForCompactSched(events);
 
-  auto table = std::unique_ptr<ProtoTranslationTable>(
-      new ProtoTranslationTable(ftrace_procfs, events, std::move(common_fields),
-                                header_spec, compact_sched));
+  std::string text = ftrace_procfs->ReadPrintkFormats();
+  PrintkMap printk_formats = ParsePrintkFormats(text);
+
+  auto table = std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
+      ftrace_procfs, events, std::move(common_fields), header_spec,
+      compact_sched, std::move(printk_formats)));
   return table;
 }
 
@@ -464,13 +477,15 @@
     const std::vector<Event>& events,
     std::vector<Field> common_fields,
     FtracePageHeaderSpec ftrace_page_header_spec,
-    CompactSchedEventFormat compact_sched_format)
+    CompactSchedEventFormat compact_sched_format,
+    PrintkMap printk_formats)
     : ftrace_procfs_(ftrace_procfs),
       events_(BuildEventsDeque(events)),
       largest_id_(events_.size() - 1),
       common_fields_(std::move(common_fields)),
       ftrace_page_header_spec_(ftrace_page_header_spec),
-      compact_sched_format_(compact_sched_format) {
+      compact_sched_format_(compact_sched_format),
+      printk_formats_(printk_formats) {
   for (const Event& event : events) {
     group_and_name_to_event_[GroupAndName(event.group, event.name)] =
         &events_.at(event.ftrace_event_id);
diff --git a/src/traced/probes/ftrace/proto_translation_table.h b/src/traced/probes/ftrace/proto_translation_table.h
index 2cf0926..c822cd5 100644
--- a/src/traced/probes/ftrace/proto_translation_table.h
+++ b/src/traced/probes/ftrace/proto_translation_table.h
@@ -30,7 +30,8 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/event_info.h"
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
+#include "src/traced/probes/ftrace/printk_formats_parser.h"
 
 namespace perfetto {
 
@@ -99,7 +100,8 @@
                         const std::vector<Event>& events,
                         std::vector<Field> common_fields,
                         FtracePageHeaderSpec ftrace_page_header_spec,
-                        CompactSchedEventFormat compact_sched_format);
+                        CompactSchedEventFormat compact_sched_format,
+                        PrintkMap printk_formats);
 
   size_t largest_id() const { return largest_id_; }
 
@@ -164,6 +166,10 @@
     return compact_sched_format_;
   }
 
+  base::StringView LookupTraceString(uint64_t address) const {
+    return printk_formats_.at(address);
+  }
+
  private:
   ProtoTranslationTable(const ProtoTranslationTable&) = delete;
   ProtoTranslationTable& operator=(const ProtoTranslationTable&) = delete;
@@ -184,6 +190,7 @@
   FtracePageHeaderSpec ftrace_page_header_spec_{};
   std::set<std::string> interned_strings_;
   CompactSchedEventFormat compact_sched_format_;
+  PrintkMap printk_formats_;
 };
 
 // Class for efficient 'is event with id x enabled?' checks.
diff --git a/src/traced/probes/ftrace/proto_translation_table_unittest.cc b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
index 1ca013a..0210f71 100644
--- a/src/traced/probes/ftrace/proto_translation_table_unittest.cc
+++ b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
@@ -397,7 +397,7 @@
   ProtoTranslationTable table(
       &ftrace, events, std::move(common_fields),
       ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-      InvalidCompactSchedEventFormatForTesting());
+      InvalidCompactSchedEventFormatForTesting(), PrintkMap());
 
   EXPECT_EQ(table.largest_id(), 100ul);
   EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "foo")), 1ul);
diff --git a/src/traced/probes/ftrace/test/data/synthetic/available_events b/src/traced/probes/ftrace/test/data/synthetic/available_events
index 500dcb2..b20b698 100644
--- a/src/traced/probes/ftrace/test/data/synthetic/available_events
+++ b/src/traced/probes/ftrace/test/data/synthetic/available_events
@@ -7,3 +7,7 @@
 clk:clk_disable
 clk:clk_set_rate
 sde:tracing_mark_write
+fastrpc:fastrpc_dma_stat
+dpu:tracing_mark_write
+g2d:tracing_mark_write
+power:suspend_resume
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/format b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/format
new file mode 100644
index 0000000..fd990fb
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/format
@@ -0,0 +1,14 @@
+name: cpuhp_enter
+ID: 33
+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:unsigned int cpu;	offset:8;	size:4;	signed:0;
+	field:int target;	offset:12;	size:4;	signed:1;
+	field:int idx;	offset:16;	size:4;	signed:1;
+	field:void * fun;	offset:24;	size:8;	signed:0;
+
+print fmt: "cpu: %04u target: %3d step: %3d (%pf)", REC->cpu, REC->target, REC->idx, REC->fun
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/id b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/id
new file mode 100644
index 0000000..bb95160
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_enter/id
@@ -0,0 +1 @@
+33
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/format b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/format
new file mode 100644
index 0000000..ec5fdde
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/format
@@ -0,0 +1,14 @@
+name: cpuhp_exit
+ID: 34
+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:unsigned int cpu;	offset:8;	size:4;	signed:0;
+	field:int state;	offset:12;	size:4;	signed:1;
+	field:int idx;	offset:16;	size:4;	signed:1;
+	field:int ret;	offset:20;	size:4;	signed:1;
+
+print fmt: " cpu: %04u  state: %3d step: %3d ret: %d", REC->cpu, REC->state, REC->idx, REC->ret
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/id b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/id
new file mode 100644
index 0000000..a787364
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_exit/id
@@ -0,0 +1 @@
+34
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/format b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/format
new file mode 100644
index 0000000..9d01304
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/format
@@ -0,0 +1,14 @@
+name: cpuhp_latency
+ID: 35
+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:unsigned int cpu;	offset:8;	size:4;	signed:0;
+	field:unsigned int state;	offset:12;	size:4;	signed:0;
+	field:u64 time;	offset:16;	size:8;	signed:0;
+	field:int ret;	offset:24;	size:4;	signed:1;
+
+print fmt: " cpu:%d state:%s latency:%llu USEC ret: %d", REC->cpu, REC->state ? "online" : "offline", REC->time, REC->ret
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/id b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/id
new file mode 100644
index 0000000..8f92bfd
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_latency/id
@@ -0,0 +1 @@
+35
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/format b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/format
new file mode 100644
index 0000000..fef4f62
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/format
@@ -0,0 +1,14 @@
+name: cpuhp_multi_enter
+ID: 36
+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:unsigned int cpu;	offset:8;	size:4;	signed:0;
+	field:int target;	offset:12;	size:4;	signed:1;
+	field:int idx;	offset:16;	size:4;	signed:1;
+	field:void * fun;	offset:24;	size:8;	signed:0;
+
+print fmt: "cpu: %04u target: %3d step: %3d (%pf)", REC->cpu, REC->target, REC->idx, REC->fun
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/id b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/id
new file mode 100644
index 0000000..7facc89
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/cpuhp/cpuhp_multi_enter/id
@@ -0,0 +1 @@
+36
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/dpu/tracing_mark_write/format b/src/traced/probes/ftrace/test/data/synthetic/events/dpu/tracing_mark_write/format
new file mode 100644
index 0000000..51e0e8b
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/dpu/tracing_mark_write/format
@@ -0,0 +1,14 @@
+name: tracing_mark_write
+ID: 1280
+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:char type;	offset:8;	size:1;	signed:0;
+	field:int pid;	offset:12;	size:4;	signed:1;
+	field:__data_loc char[] name;	offset:16;	size:4;	signed:0;
+	field:int value;	offset:20;	size:4;	signed:1;
+
+print fmt: "%c|%d|%s|%d", REC->type, REC->pid, __get_str(name), REC->value
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/fastrpc/fastrpc_dma_stat/format b/src/traced/probes/ftrace/test/data/synthetic/events/fastrpc/fastrpc_dma_stat/format
new file mode 100644
index 0000000..d9a922f
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/fastrpc/fastrpc_dma_stat/format
@@ -0,0 +1,13 @@
+name: fastrpc_dma_stat
+ID: 1039
+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:int cid;	offset:8;	size:4;	signed:1;
+	field:long len;	offset:16;	size:8;	signed:1;
+	field:unsigned long total_allocated;	offset:24;	size:8;	signed:0;
+
+print fmt: "cid=%u len=%ldB total_allocated=%ldB", REC->cid, REC->len, REC->total_allocated
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/g2d/tracing_mark_write/format b/src/traced/probes/ftrace/test/data/synthetic/events/g2d/tracing_mark_write/format
new file mode 100644
index 0000000..f17c68b
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/g2d/tracing_mark_write/format
@@ -0,0 +1,14 @@
+name: tracing_mark_write
+ID: 1202
+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:char type;	offset:8;	size:1;	signed:0;
+	field:int pid;	offset:12;	size:4;	signed:1;
+	field:__data_loc char[] name;	offset:16;	size:4;	signed:0;
+	field:int value;	offset:20;	size:4;	signed:1;
+
+print fmt: "%c|%d|%s|%d", REC->type, REC->pid, __get_str(name), REC->value
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format b/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format
new file mode 100644
index 0000000..aabfb0b
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format
@@ -0,0 +1,13 @@
+name: suspend_resume
+ID: 172
+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:const char * action;	offset:8;	size:8;	signed:0;
+	field:int val;	offset:16;	size:4;	signed:1;
+	field:bool start;	offset:20;	size:1;	signed:0;
+
+print fmt: "%s[%u] %s", REC->action, (unsigned int)REC->val, (REC->start)?"begin":"end"
diff --git a/src/traced/probes/ftrace/test/data/synthetic/printk_formats b/src/traced/probes/ftrace/test/data/synthetic/printk_formats
new file mode 100644
index 0000000..5f0f8c6
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/printk_formats
@@ -0,0 +1,5 @@
+0xffffff850501d52e : "Entries can be duplicated"
+0xffffff8504f57a1c : "freeze_processes"
+0xffffff8504f57a1c : "freeze_processes"
+0xffffff8504f57a5a : "sync_filesystems"
+0xffffff8504f57a4c : "suspend_enter"
diff --git a/src/traced/probes/ftrace/test/explorer.html b/src/traced/probes/ftrace/test/explorer.html
index a567b10..a474df5 100755
--- a/src/traced/probes/ftrace/test/explorer.html
+++ b/src/traced/probes/ftrace/test/explorer.html
@@ -160,12 +160,30 @@
   });
 }
 
-function geturl(url) {
+function delay(t, v) {
+ return new Promise(resolve => { 
+   setTimeout(resolve.bind(null, v), t)
+ });
+}
+
+// Limit the number of outstanding fetch requests to avoid causing
+// problems in the browser.
+let GET_URL_TOKENS = 400;
+async function geturl(url) {
   console.log('Fetch:', url);
   if (gCache.has(url)) return Promise.resolve(gCache.get(url));
+
+  while (GET_URL_TOKENS === 0) {
+    // Retry in 1000ms +/- 250ms to avoid all the requests lining up.
+    await delay(1000 + 500 * (Math.random() - 0.5));
+  }
+  GET_URL_TOKENS -= 1;
+
   return fetch(url).then(r => r.text()).then(text => {
     gCache.set(url, text);
     return text;
+  }).finally(() => {
+    GET_URL_TOKENS += 1;
   });
 }
 
@@ -242,7 +260,7 @@
   return m('.context', [
     m('h1', {class: 'title'}, 'Ftrace Format Explorer'),
     m('input[type=text][placeholder=Filter]', {
-      oninput: m.withAttr('value', value => gFilterText = value),
+      oninput: e => gFilterText = e.target.value,
       value: filterText,
     }),
     m('ul',
@@ -266,7 +284,7 @@
   if (!r2) r2 = records[0];
   let f1 = getfiletext(r1.url);
   let f2 = getfiletext(r2.url);
-  let diff = JsDiff.diffChars(f1, f2);
+  let diff = Diff.diffChars(f1, f2);
 
   let es = diff.map(part => {
     let color = part.added ? 'green' : part.removed ? 'red' : 'grey';
diff --git a/src/traced/probes/ftrace/test/test_messages.proto b/src/traced/probes/ftrace/test/test_messages.proto
index 1e6ece4..3d584d9 100644
--- a/src/traced/probes/ftrace/test/test_messages.proto
+++ b/src/traced/probes/ftrace/test/test_messages.proto
@@ -32,4 +32,5 @@
   optional string field_char_16 = 500;
   optional string field_char = 501;
   optional string field_data_loc = 502;
+  optional string field_char_star = 503;
 }
diff --git a/src/traced/probes/initial_display_state/initial_display_state_data_source.cc b/src/traced/probes/initial_display_state/initial_display_state_data_source.cc
index 08bb0fa..f5b9583 100644
--- a/src/traced/probes/initial_display_state/initial_display_state_data_source.cc
+++ b/src/traced/probes/initial_display_state/initial_display_state_data_source.cc
@@ -73,7 +73,8 @@
     auto weak_this = GetWeakPtr();
 
     uint32_t delay_ms =
-        poll_period_ms_ - (base::GetWallTimeMs().count() % poll_period_ms_);
+        poll_period_ms_ -
+        static_cast<uint32_t>(base::GetWallTimeMs().count() % poll_period_ms_);
     task_runner_->PostDelayedTask(
         [weak_this]() -> void {
           if (weak_this) {
diff --git a/src/traced/probes/packages_list/BUILD.gn b/src/traced/probes/packages_list/BUILD.gn
index 0f17596..669fa18 100644
--- a/src/traced/probes/packages_list/BUILD.gn
+++ b/src/traced/probes/packages_list/BUILD.gn
@@ -14,9 +14,21 @@
 
 import("../../../../gn/test.gni")
 
+source_set("packages_list_parser") {
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../base",
+  ]
+  sources = [
+    "packages_list_parser.cc",
+    "packages_list_parser.h",
+  ]
+}
+
 source_set("packages_list") {
   public_deps = [ "../../../tracing/core" ]
   deps = [
+    ":packages_list_parser",
     "..:data_source",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/traced",
@@ -36,6 +48,7 @@
   testonly = true
   deps = [
     ":packages_list",
+    ":packages_list_parser",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
     "../../../../protos/perfetto/trace/android:cpp",
@@ -43,5 +56,5 @@
     "../../../../src/base:test_support",
     "../../../../src/tracing/test:test_support",
   ]
-  sources = [ "packages_list_data_source_unittest.cc" ]
+  sources = [ "packages_list_unittest.cc" ]
 }
diff --git a/src/traced/probes/packages_list/packages_list_data_source.cc b/src/traced/probes/packages_list/packages_list_data_source.cc
index 68fa662..f9919b8 100644
--- a/src/traced/probes/packages_list/packages_list_data_source.cc
+++ b/src/traced/probes/packages_list/packages_list_data_source.cc
@@ -22,6 +22,8 @@
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
 using perfetto::protos::pbzero::PackagesListConfig;
 
 namespace perfetto {
@@ -57,60 +59,6 @@
   return parsed_fully;
 }
 
-bool ReadPackagesListLine(char* line, Package* package) {
-  size_t idx = 0;
-  for (base::StringSplitter ss(line, ' '); ss.Next();) {
-    switch (idx) {
-      case 0:
-        package->name = std::string(ss.cur_token(), ss.cur_token_size());
-        break;
-      case 1: {
-        char* end;
-        long long uid = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list uid.");
-          return false;
-        }
-        package->uid = static_cast<uint64_t>(uid);
-        break;
-      }
-      case 2: {
-        char* end;
-        long long debuggable = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list debuggable.");
-          return false;
-        }
-        package->debuggable = debuggable != 0;
-        break;
-      }
-      case 6: {
-        char* end;
-        long long profilable_from_shell = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list profilable_from_shell.");
-          return false;
-        }
-        package->profileable_from_shell = profilable_from_shell != 0;
-        break;
-      }
-      case 7: {
-        char* end;
-        long long version_code = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list version_code: %s.",
-                        ss.cur_token());
-          return false;
-        }
-        package->version_code = version_code;
-        break;
-      }
-    }
-    ++idx;
-  }
-  return true;
-}
-
 PackagesListDataSource::PackagesListDataSource(
     const DataSourceConfig& ds_config,
     TracingSessionID session_id,
diff --git a/src/traced/probes/packages_list/packages_list_data_source.h b/src/traced/probes/packages_list/packages_list_data_source.h
index 5dd3fdc..b19472b 100644
--- a/src/traced/probes/packages_list/packages_list_data_source.h
+++ b/src/traced/probes/packages_list/packages_list_data_source.h
@@ -35,15 +35,6 @@
 
 class TraceWriter;
 
-struct Package {
-  std::string name;
-  uint64_t uid = 0;
-  bool debuggable = false;
-  bool profileable_from_shell = false;
-  int64_t version_code = 0;
-};
-
-bool ReadPackagesListLine(char* line, Package* package);
 bool ParsePackagesListStream(protos::pbzero::PackagesList* packages_list,
                              const base::ScopedFstream& fs,
                              const std::set<std::string>& package_name_filter);
diff --git a/src/traced/probes/packages_list/packages_list_parser.cc b/src/traced/probes/packages_list/packages_list_parser.cc
new file mode 100644
index 0000000..bcbccaa
--- /dev/null
+++ b/src/traced/probes/packages_list/packages_list_parser.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+
+namespace perfetto {
+
+bool ReadPackagesListLine(char* line, Package* package) {
+  size_t idx = 0;
+  for (base::StringSplitter ss(line, ' '); ss.Next();) {
+    switch (idx) {
+      case 0:
+        package->name = std::string(ss.cur_token(), ss.cur_token_size());
+        break;
+      case 1: {
+        char* end;
+        long long uid = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list uid.");
+          return false;
+        }
+        package->uid = static_cast<uint64_t>(uid);
+        break;
+      }
+      case 2: {
+        char* end;
+        long long debuggable = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list debuggable.");
+          return false;
+        }
+        package->debuggable = debuggable != 0;
+        break;
+      }
+      case 6: {
+        char* end;
+        long long profilable_from_shell = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list profilable_from_shell.");
+          return false;
+        }
+        package->profileable_from_shell = profilable_from_shell != 0;
+        break;
+      }
+      case 7: {
+        char* end;
+        long long version_code = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list version_code: %s.",
+                        ss.cur_token());
+          return false;
+        }
+        package->version_code = version_code;
+        break;
+      }
+    }
+    ++idx;
+  }
+  return true;
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/packages_list/packages_list_parser.h b/src/traced/probes/packages_list/packages_list_parser.h
new file mode 100644
index 0000000..3deb3c5
--- /dev/null
+++ b/src/traced/probes/packages_list/packages_list_parser.h
@@ -0,0 +1,36 @@
+/*
+ * 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_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
+#define SRC_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
+
+#include <inttypes.h>
+#include <string>
+
+namespace perfetto {
+
+struct Package {
+  std::string name;
+  uint64_t uid = 0;
+  bool debuggable = false;
+  bool profileable_from_shell = false;
+  int64_t version_code = 0;
+};
+
+bool ReadPackagesListLine(char* line, Package* package);
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
diff --git a/src/traced/probes/packages_list/packages_list_data_source_unittest.cc b/src/traced/probes/packages_list/packages_list_unittest.cc
similarity index 98%
rename from src/traced/probes/packages_list/packages_list_data_source_unittest.cc
rename to src/traced/probes/packages_list/packages_list_unittest.cc
index cb311d2..acfc791 100644
--- a/src/traced/probes/packages_list/packages_list_data_source_unittest.cc
+++ b/src/traced/probes/packages_list/packages_list_unittest.cc
@@ -25,6 +25,7 @@
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/android/packages_list.gen.h"
 #include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "src/traced/probes/packages_list/packages_list_parser.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/traced/probes/power/android_power_data_source.cc b/src/traced/probes/power/android_power_data_source.cc
index 0b4e749..5f55183 100644
--- a/src/traced/probes/power/android_power_data_source.cc
+++ b/src/traced/probes/power/android_power_data_source.cc
@@ -149,7 +149,7 @@
         if (weak_this)
           weak_this->Tick();
       },
-      poll_interval_ms_ - (now_ms % poll_interval_ms_));
+      poll_interval_ms_ - static_cast<uint32_t>(now_ms % poll_interval_ms_));
 
   WriteBatteryCounters();
   WritePowerRailsData();
@@ -208,7 +208,7 @@
     // all rail names etc. on each one.
     rail_descriptors_logged_ = true;
     auto rail_descriptors = lib_->GetRailDescriptors();
-    if (rail_descriptors.size() == 0) {
+    if (rail_descriptors.empty()) {
       // No rails to collect data for. Don't try again in the next iteration.
       rails_collection_enabled_ = false;
       return;
diff --git a/src/traced/probes/probes.cc b/src/traced/probes/probes.cc
index b05fd02..883d944 100644
--- a/src/traced/probes/probes.cc
+++ b/src/traced/probes/probes.cc
@@ -22,18 +22,13 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/version.h"
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 
 #include "src/traced/probes/ftrace/ftrace_procfs.h"
 #include "src/traced/probes/probes_producer.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
-#include "perfetto_version.gen.h"
-#else
-#define PERFETTO_GET_GIT_REVISION() "unknown"
-#endif
-
 namespace perfetto {
 
 int __attribute__((visibility("default"))) ProbesMain(int argc, char** argv) {
@@ -42,7 +37,7 @@
     OPT_VERSION,
   };
 
-  static const struct option long_options[] = {
+  static const option long_options[] = {
       {"cleanup-after-crash", no_argument, nullptr, OPT_CLEANUP_AFTER_CRASH},
       {"version", no_argument, nullptr, OPT_VERSION},
       {nullptr, 0, nullptr, 0}};
@@ -57,7 +52,7 @@
         HardResetFtraceState();
         return 0;
       case OPT_VERSION:
-        printf("%s\n", PERFETTO_GET_GIT_REVISION());
+        printf("%s\n", base::GetVersionString());
         return 0;
       default:
         PERFETTO_ELOG("Usage: %s [--cleanup-after-crash|--version]", argv[0]);
diff --git a/src/traced/probes/probes_producer.cc b/src/traced/probes/probes_producer.cc
index 69072c7..58389b8 100644
--- a/src/traced/probes/probes_producer.cc
+++ b/src/traced/probes/probes_producer.cc
@@ -129,7 +129,6 @@
   // to reset all the data sources. Trying to handle that manually is going to
   // be error prone. What we do here is simply destroying the instance and
   // recreating it again.
-  // TODO(hjd): Add e2e test for this.
 
   base::TaskRunner* task_runner = task_runner_;
   const char* socket_name = socket_name_;
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index 6adb342..9dafa72 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -54,27 +54,13 @@
 // was provided in the config. The cache is trimmed if it exceeds this size.
 const size_t kThreadTimeInStateCacheSize = 10000;
 
-inline int32_t ParseIntValue(const char* str) {
-  int32_t ret = 0;
-  for (;;) {
-    char c = *(str++);
-    if (!c)
-      break;
-    if (c < '0' || c > '9')
-      return 0;
-    ret *= 10;
-    ret += static_cast<int32_t>(c - '0');
-  }
-  return ret;
-}
-
 int32_t ReadNextNumericDir(DIR* dirp) {
   while (struct dirent* dir_ent = readdir(dirp)) {
     if (dir_ent->d_type != DT_DIR)
       continue;
-    int32_t int_value = ParseIntValue(dir_ent->d_name);
+    auto int_value = base::CStringToInt32(dir_ent->d_name);
     if (int_value)
-      return int_value;
+      return *int_value;
   }
   return 0;
 }
@@ -383,7 +369,9 @@
     return;
   ProcessStatsDataSource& thiz = *weak_this;
   uint32_t period_ms = thiz.poll_period_ms_;
-  uint32_t delay_ms = period_ms - (base::GetWallTimeMs().count() % period_ms);
+  uint32_t delay_ms =
+      period_ms -
+      static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
   thiz.task_runner_->PostDelayedTask(
       std::bind(&ProcessStatsDataSource::Tick, weak_this), delay_ms);
   thiz.WriteAllProcessStats();
@@ -399,9 +387,9 @@
 }
 
 void ProcessStatsDataSource::WriteAllProcessStats() {
-  // TODO(primiano): implement whitelisting of processes by names.
+  // TODO(primiano): implement filtering of processes by names.
   // TODO(primiano): Have a pid cache to avoid wasting cycles reading kthreads
-  // proc files over and over. Same for non-whitelist processes (see above).
+  // proc files over and over. Same for non-filtered processes (see above).
 
   CacheProcFsScanStartTimestamp();
   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, PS_WRITE_ALL_PROCESS_STATS);
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 9278e1c..bb123d1 100644
--- a/src/traced/probes/ps/process_stats_data_source_unittest.cc
+++ b/src/traced/probes/ps/process_stats_data_source_unittest.cc
@@ -18,6 +18,7 @@
 
 #include <dirent.h>
 
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/tracing/core/data_source_config.h"
@@ -393,7 +394,7 @@
 
   // Cleanup |fake_proc|. TempDir checks that the directory is empty.
   for (std::string& path : dirs_to_delete)
-    rmdir(path.c_str());
+    base::Rmdir(path);
 }
 
 TEST_F(ProcessStatsDataSourceTest, CacheProcessStats) {
@@ -461,7 +462,7 @@
   }
 
   // Cleanup |fake_proc|. TempDir checks that the directory is empty.
-  rmdir(path);
+  base::Rmdir(path);
 }
 
 TEST_F(ProcessStatsDataSourceTest, ThreadTimeInState) {
@@ -649,7 +650,7 @@
   EXPECT_THAT(thread.cpu_freq_full(), true);
 
   for (const std::string& path : dirs_to_delete)
-    rmdir(path.c_str());
+    base::Rmdir(path);
 }
 
 }  // namespace
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index 2d939ec..f900db8 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -179,7 +179,9 @@
   SysStatsDataSource& thiz = *weak_this;
 
   uint32_t period_ms = thiz.tick_period_ms_;
-  uint32_t delay_ms = period_ms - (base::GetWallTimeMs().count() % period_ms);
+  uint32_t delay_ms =
+      period_ms -
+      static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
   thiz.task_runner_->PostDelayedTask(
       std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
   thiz.ReadSysStats();
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
index 49af8a4..38a7414 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source_unittest.cc
@@ -130,6 +130,7 @@
 pgsteal_kswapd_dma 19471476
 pgsteal_kswapd_normal 21138380
 pgsteal_kswapd_movable 0
+pgsteal_direct 91537
 pgsteal_direct_dma 40625
 pgsteal_direct_normal 50912
 pgsteal_direct_movable 0
@@ -290,6 +291,7 @@
   sys_cfg.add_vmstat_counters(C::VMSTAT_NR_FREE_PAGES);
   sys_cfg.add_vmstat_counters(C::VMSTAT_PGACTIVATE);
   sys_cfg.add_vmstat_counters(C::VMSTAT_PGMIGRATE_FAIL);
+  sys_cfg.add_vmstat_counters(C::VMSTAT_PGSTEAL_DIRECT);
   config.set_sys_stats_config_raw(sys_cfg.SerializeAsString());
   auto data_source = GetSysStatsDataSource(config);
 
@@ -308,7 +310,9 @@
 
   EXPECT_THAT(kvs, UnorderedElementsAre(KV{C::VMSTAT_NR_FREE_PAGES, 16449},  //
                                         KV{C::VMSTAT_PGACTIVATE, 11897892},  //
-                                        KV{C::VMSTAT_PGMIGRATE_FAIL, 3439}));
+                                        KV{C::VMSTAT_PGMIGRATE_FAIL, 3439},  //
+                                        KV{C::VMSTAT_PGSTEAL_DIRECT, 91537}  //
+                                        ));
 }
 
 TEST_F(SysStatsDataSourceTest, VmstatAll) {
diff --git a/src/traced/service/BUILD.gn b/src/traced/service/BUILD.gn
index 2b7798b..2ff0007 100644
--- a/src/traced/service/BUILD.gn
+++ b/src/traced/service/BUILD.gn
@@ -42,9 +42,6 @@
     "../../tracing/core:service",
     "../../tracing/ipc/service",
   ]
-  if (enable_perfetto_version_gen) {
-    deps += [ "//gn/standalone:gen_git_revision" ]
-  }
   sources = [
     "builtin_producer.cc",
     "builtin_producer.h",
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 4a876f5..523c6c3 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -16,31 +16,99 @@
 
 #include <getopt.h>
 #include <stdio.h>
+#include <algorithm>
 
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/base/version.h"
 #include "perfetto/ext/base/watchdog.h"
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 #include "perfetto/ext/tracing/ipc/service_ipc_host.h"
 #include "src/traced/service/builtin_producer.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
-#include "perfetto_version.gen.h"
-#else
-#define PERFETTO_GET_GIT_REVISION() "unknown"
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#define PERFETTO_SET_SOCKET_PERMISSIONS
+#include <fcntl.h>
+#include <grp.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 #endif
 
 namespace perfetto {
+namespace {
+#if defined(PERFETTO_SET_SOCKET_PERMISSIONS)
+void SetSocketPermissions(const std::string& socket_name,
+                          const std::string& group_name,
+                          const std::string& mode_bits) {
+  PERFETTO_CHECK(!socket_name.empty());
+  PERFETTO_CHECK(!group_name.empty());
+  struct group* socket_group = nullptr;
+  // Query the group ID of |group|.
+  do {
+    socket_group = getgrnam(group_name.c_str());
+  } while (socket_group == nullptr && errno == EINTR);
+  if (socket_group == nullptr) {
+    PERFETTO_FATAL("Failed to get group information of %s ",
+                   group_name.c_str());
+  }
+
+  if (PERFETTO_EINTR(
+          chown(socket_name.c_str(), geteuid(), socket_group->gr_gid))) {
+    PERFETTO_FATAL("Failed to chown %s ", socket_name.c_str());
+  }
+
+  // |mode| accepts values like "0660" as "rw-rw----" mode bits.
+  auto mode_value = base::StringToInt32(mode_bits, 8);
+  if (!(mode_bits.size() == 4 && mode_value.has_value())) {
+    PERFETTO_FATAL(
+        "The chmod option must be a 4-digit octal number, e.g. 0660");
+  }
+  if (PERFETTO_EINTR(chmod(socket_name.c_str(),
+                           static_cast<mode_t>(mode_value.value())))) {
+    PERFETTO_FATAL("Failed to chmod %s", socket_name.c_str());
+  }
+}
+#endif  // defined(PERFETTO_SET_SOCKET_PERMISSIONS)
+
+void PrintUsage(const char* prog_name) {
+  PERFETTO_ELOG(R"(
+Usage: %s [option] ...
+Options and arguments
+    --version : print the version number and exit.
+    --set-socket-permissions <permissions> : sets group ownership and permission
+        mode bits of the producer and consumer sockets.
+        <permissions> format: <prod_group>:<prod_mode>:<cons_group>:<cons_mode>,
+        where <prod_group> is the group name for chgrp the producer socket,
+        <prod_mode> is the mode bits (e.g. 0660) for chmod the produce socket,
+        <cons_group> is the group name for chgrp the consumer socket, and
+        <cons_mode> is the mode bits (e.g. 0660) for chmod the consumer socket.
+Example: %s --set-socket-permissions traced-producer:0660:traced-consumer:0660
+    starts the service and sets the group ownership of the producer and consumer
+    sockets to "traced-producer" and "traced-consumer", respectively. Both
+    producer and consumer sockets are chmod with 0660  (rw-rw----) mode bits.
+)",
+                prog_name, prog_name);
+}
+}  // namespace
 
 int __attribute__((visibility("default"))) ServiceMain(int argc, char** argv) {
   enum LongOption {
     OPT_VERSION = 1000,
+    OPT_SET_SOCKET_PERMISSIONS = 1001,
   };
 
-  static const struct option long_options[] = {
+  static const option long_options[] = {
       {"version", no_argument, nullptr, OPT_VERSION},
+      {"set-socket-permissions", required_argument, nullptr,
+       OPT_SET_SOCKET_PERMISSIONS},
       {nullptr, 0, nullptr, 0}};
 
+  std::string producer_socket_group, consumer_socket_group,
+      producer_socket_mode, consumer_socket_mode;
+
   int option_index;
   for (;;) {
     int option = getopt_long(argc, argv, "", long_options, &option_index);
@@ -48,10 +116,23 @@
       break;
     switch (option) {
       case OPT_VERSION:
-        printf("%s\n", PERFETTO_GET_GIT_REVISION());
+        printf("%s\n", base::GetVersionString());
         return 0;
+      case OPT_SET_SOCKET_PERMISSIONS: {
+        // Check that the socket permission argument is well formed.
+        auto parts = base::SplitString(std::string(optarg), ":");
+        PERFETTO_CHECK(parts.size() == 4);
+        PERFETTO_CHECK(
+            std::all_of(parts.cbegin(), parts.cend(),
+                        [](const std::string& part) { return !part.empty(); }));
+        producer_socket_group = parts[0];
+        producer_socket_mode = parts[1];
+        consumer_socket_group = parts[2];
+        consumer_socket_mode = parts[3];
+        break;
+      }
       default:
-        PERFETTO_ELOG("Usage: %s [--version]", argv[0]);
+        PrintUsage(argv[0]);
         return 1;
     }
   }
@@ -75,6 +156,19 @@
     unlink(GetProducerSocket());
     unlink(GetConsumerSocket());
     started = svc->Start(GetProducerSocket(), GetConsumerSocket());
+
+    if (!producer_socket_group.empty()) {
+#if defined(PERFETTO_SET_SOCKET_PERMISSIONS)
+      SetSocketPermissions(GetProducerSocket(), producer_socket_group,
+                           producer_socket_mode);
+      SetSocketPermissions(GetConsumerSocket(), consumer_socket_group,
+                           consumer_socket_mode);
+#else
+      PERFETTO_ELOG(
+          "Setting socket permissions is not supported on this platform");
+      return 1;
+#endif
+    }
   }
 
   if (!started) {
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index faf2dc7..8588bf1 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -90,6 +90,7 @@
     "../../include/perfetto/tracing/core",
     "../../protos/perfetto/common:zero",
     "../../protos/perfetto/config:cpp",
+    "../../protos/perfetto/config/interceptors:zero",
     "../../protos/perfetto/config/track_event:cpp",
     "../base",
     "core",
@@ -99,9 +100,12 @@
     "../../include/perfetto/tracing",
   ]
   sources = [
+    "console_interceptor.cc",
     "data_source.cc",
     "debug_annotation.cc",
     "event_context.cc",
+    "interceptor.cc",
+    "internal/interceptor_trace_writer.cc",
     "internal/tracing_muxer_impl.cc",
     "internal/tracing_muxer_impl.h",
     "internal/track_event_internal.cc",
@@ -110,6 +114,7 @@
     "track.cc",
     "track_event_category_registry.cc",
     "track_event_legacy.cc",
+    "track_event_state_tracker.cc",
     "virtual_destructors.cc",
   ]
   assert_no_deps = [ "core:service" ]
diff --git a/src/tracing/console_interceptor.cc b/src/tracing/console_interceptor.cc
new file mode 100644
index 0000000..8dda4ff
--- /dev/null
+++ b/src/tracing/console_interceptor.cc
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/console_interceptor.h"
+
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/hash.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
+
+#include "protos/perfetto/common/interceptor_descriptor.gen.h"
+#include "protos/perfetto/config/data_source_config.gen.h"
+#include "protos/perfetto/config/interceptor_config.gen.h"
+#include "protos/perfetto/config/interceptors/console_config.pbzero.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+#include <algorithm>
+#include <cmath>
+#include <tuple>
+
+namespace perfetto {
+
+// sRGB color.
+struct ConsoleColor {
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+};
+
+namespace {
+
+int g_output_fd_for_testing;
+
+// Google Turbo colormap.
+constexpr std::array<ConsoleColor, 16> kTurboColors = {{
+    ConsoleColor{0x30, 0x12, 0x3b},
+    ConsoleColor{0x40, 0x40, 0xa1},
+    ConsoleColor{0x46, 0x6b, 0xe3},
+    ConsoleColor{0x41, 0x93, 0xfe},
+    ConsoleColor{0x28, 0xbb, 0xeb},
+    ConsoleColor{0x17, 0xdc, 0xc2},
+    ConsoleColor{0x32, 0xf1, 0x97},
+    ConsoleColor{0x6d, 0xfd, 0x62},
+    ConsoleColor{0xa4, 0xfc, 0x3b},
+    ConsoleColor{0xcd, 0xeb, 0x34},
+    ConsoleColor{0xed, 0xcf, 0x39},
+    ConsoleColor{0xfd, 0xab, 0x33},
+    ConsoleColor{0xfa, 0x7d, 0x20},
+    ConsoleColor{0xea, 0x50, 0x0d},
+    ConsoleColor{0xd0, 0x2f, 0x04},
+    ConsoleColor{0xa9, 0x15, 0x01},
+}};
+
+constexpr size_t kHueBits = 4;
+constexpr uint32_t kMaxHue = kTurboColors.size() << kHueBits;
+constexpr uint8_t kLightness = 128u;
+constexpr ConsoleColor kWhiteColor{0xff, 0xff, 0xff};
+
+const char kDim[] = "\x1b[90m";
+const char kDefault[] = "\x1b[39m";
+const char kReset[] = "\x1b[0m";
+
+#define FMT_RGB_SET "\x1b[38;2;%d;%d;%dm"
+#define FMT_RGB_SET_BG "\x1b[48;2;%d;%d;%dm"
+
+ConsoleColor Mix(ConsoleColor a, ConsoleColor b, uint8_t ratio) {
+  return {
+      static_cast<uint8_t>(a.r + (((b.r - a.r) * ratio) >> 8)),
+      static_cast<uint8_t>(a.g + (((b.g - a.g) * ratio) >> 8)),
+      static_cast<uint8_t>(a.b + (((b.b - a.b) * ratio) >> 8)),
+  };
+}
+
+ConsoleColor HueToRGB(uint32_t hue) {
+  PERFETTO_DCHECK(hue < kMaxHue);
+  uint32_t c1 = hue >> kHueBits;
+  uint32_t c2 =
+      std::min(static_cast<uint32_t>(kTurboColors.size() - 1), c1 + 1u);
+  uint32_t ratio = hue & ((1 << kHueBits) - 1);
+  return Mix(kTurboColors[c1], kTurboColors[c2],
+             static_cast<uint8_t>(ratio | (ratio << kHueBits)));
+}
+
+uint32_t CounterToHue(uint32_t counter) {
+  // We split the hue space into 8 segments, reversing the order of bits so
+  // successive counter values will be far from each other.
+  uint32_t reversed =
+      ((counter & 0x7) >> 2) | ((counter & 0x3)) | ((counter & 0x1) << 2);
+  return reversed * kMaxHue / 8;
+}
+
+}  // namespace
+
+class ConsoleInterceptor::Delegate : public TrackEventStateTracker::Delegate {
+ public:
+  explicit Delegate(InterceptorContext&);
+  ~Delegate() override;
+
+  TrackEventStateTracker::SessionState* GetSessionState() override;
+  void OnTrackUpdated(TrackEventStateTracker::Track&) override;
+  void OnTrackEvent(const TrackEventStateTracker::Track&,
+                    const TrackEventStateTracker::ParsedTrackEvent&) override;
+
+ private:
+  using SelfHandle = LockedHandle<ConsoleInterceptor>;
+
+  InterceptorContext& context_;
+  base::Optional<SelfHandle> locked_self_;
+};
+
+ConsoleInterceptor::~ConsoleInterceptor() = default;
+
+ConsoleInterceptor::ThreadLocalState::ThreadLocalState(
+    ThreadLocalStateArgs& args) {
+  if (auto self = args.GetInterceptorLocked()) {
+    start_time_ns = self->start_time_ns_;
+    use_colors = self->use_colors_;
+    fd = self->fd_;
+  }
+}
+
+ConsoleInterceptor::ThreadLocalState::~ThreadLocalState() = default;
+
+ConsoleInterceptor::Delegate::Delegate(InterceptorContext& context)
+    : context_(context) {}
+ConsoleInterceptor::Delegate::~Delegate() = default;
+
+TrackEventStateTracker::SessionState*
+ConsoleInterceptor::Delegate::GetSessionState() {
+  // When the session state is retrieved for the first time, it is cached (and
+  // kept locked) until we return from OnTracePacket. This avoids having to lock
+  // and unlock the instance multiple times per invocation.
+  if (locked_self_.has_value())
+    return &locked_self_.value()->session_state_;
+  locked_self_ =
+      base::make_optional<SelfHandle>(context_.GetInterceptorLocked());
+  return &locked_self_.value()->session_state_;
+}
+
+void ConsoleInterceptor::Delegate::OnTrackUpdated(
+    TrackEventStateTracker::Track& track) {
+  auto track_color = HueToRGB(CounterToHue(track.index));
+  std::array<char, 16> title;
+  if (!track.name.empty()) {
+    snprintf(title.data(), title.size(), "%s", track.name.c_str());
+  } else if (track.pid && track.tid) {
+    snprintf(title.data(), title.size(), "%u:%u",
+             static_cast<uint32_t>(track.pid),
+             static_cast<uint32_t>(track.tid));
+  } else if (track.pid) {
+    snprintf(title.data(), title.size(), "%" PRId64, track.pid);
+  } else {
+    snprintf(title.data(), title.size(), "%" PRIu64, track.uuid);
+  }
+  int title_width = static_cast<int>(title.size());
+
+  auto& tls = context_.GetThreadLocalState();
+  std::array<char, 128> message_prefix{};
+  ssize_t written = 0;
+  if (tls.use_colors) {
+    written = snprintf(message_prefix.data(), message_prefix.size(),
+                       FMT_RGB_SET_BG " %s%s %-*.*s", track_color.r,
+                       track_color.g, track_color.b, kReset, kDim, title_width,
+                       title_width, title.data());
+  } else {
+    written = snprintf(message_prefix.data(), message_prefix.size(), "%-*.*s",
+                       title_width + 2, title_width, title.data());
+  }
+  if (written < 0)
+    written = message_prefix.size();
+  track.user_data.assign(message_prefix.begin(),
+                         message_prefix.begin() + written);
+}
+
+void ConsoleInterceptor::Delegate::OnTrackEvent(
+    const TrackEventStateTracker::Track& track,
+    const TrackEventStateTracker::ParsedTrackEvent& event) {
+  // Start printing.
+  auto& tls = context_.GetThreadLocalState();
+  tls.buffer_pos = 0;
+
+  // Print timestamp and track identifier.
+  SetColor(context_, kDim);
+  Printf(context_, "[%7.3lf] %.*s",
+         static_cast<double>(event.timestamp_ns - tls.start_time_ns) / 1e9,
+         static_cast<int>(track.user_data.size()), track.user_data.data());
+
+  // Print category.
+  Printf(context_, "%-5.*s ",
+         std::min(5, static_cast<int>(event.category.size)),
+         event.category.data);
+
+  // Print stack depth.
+  for (size_t i = 0; i < event.stack_depth; i++) {
+    Printf(context_, "-  ");
+  }
+
+  // Print slice name.
+  auto slice_color = HueToRGB(event.name_hash % kMaxHue);
+  auto highlight_color = Mix(slice_color, kWhiteColor, kLightness);
+  if (event.track_event.type() == protos::pbzero::TrackEvent::TYPE_SLICE_END) {
+    SetColor(context_, kDefault);
+    Printf(context_, "} ");
+  }
+  SetColor(context_, highlight_color);
+  Printf(context_, "%.*s", static_cast<int>(event.name.size), event.name.data);
+  SetColor(context_, kReset);
+  if (event.track_event.type() ==
+      protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN) {
+    SetColor(context_, kDefault);
+    Printf(context_, " {");
+  }
+
+  // Print annotations.
+  if (event.track_event.has_debug_annotations()) {
+    PrintAnnotations(context_, event.track_event, slice_color, highlight_color);
+  }
+
+  // TODO(skyostil): Print typed arguments.
+
+  // Print duration for longer events.
+  constexpr uint64_t kNsPerMillisecond = 1000000u;
+  if (event.duration_ns >= 10 * kNsPerMillisecond) {
+    SetColor(context_, kDim);
+    Printf(context_, " +%" PRIu64 "ms", event.duration_ns / kNsPerMillisecond);
+  }
+  SetColor(context_, kReset);
+  Printf(context_, "\n");
+}
+
+// static
+void ConsoleInterceptor::Register() {
+  perfetto::protos::gen::InterceptorDescriptor desc;
+  desc.set_name("console");
+  Interceptor<ConsoleInterceptor>::Register(desc);
+}
+
+// static
+void ConsoleInterceptor::SetOutputFdForTesting(int fd) {
+  g_output_fd_for_testing = fd;
+}
+
+void ConsoleInterceptor::OnSetup(const SetupArgs& args) {
+  int fd = STDOUT_FILENO;
+  if (g_output_fd_for_testing)
+    fd = g_output_fd_for_testing;
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM)
+  bool use_colors = isatty(fd);
+#else
+  bool use_colors = false;
+#endif
+  protos::pbzero::ConsoleConfig::Decoder config(
+      args.config.interceptor_config().console_config_raw());
+  if (config.has_enable_colors())
+    use_colors = config.enable_colors();
+  if (config.output() == protos::pbzero::ConsoleConfig::OUTPUT_STDOUT) {
+    fd = STDOUT_FILENO;
+  } else if (config.output() == protos::pbzero::ConsoleConfig::OUTPUT_STDERR) {
+    fd = STDERR_FILENO;
+  }
+  fd_ = fd;
+  use_colors_ = use_colors;
+}
+
+void ConsoleInterceptor::OnStart(const StartArgs&) {
+  start_time_ns_ = internal::TrackEventInternal::GetTimeNs();
+}
+
+void ConsoleInterceptor::OnStop(const StopArgs&) {}
+
+// static
+void ConsoleInterceptor::OnTracePacket(InterceptorContext context) {
+  {
+    auto& tls = context.GetThreadLocalState();
+    Delegate delegate(context);
+    perfetto::protos::pbzero::TracePacket::Decoder packet(
+        context.packet_data.data, context.packet_data.size);
+    TrackEventStateTracker::ProcessTracePacket(delegate, tls.sequence_state,
+                                               packet);
+  }  // (Potential) lock scope for session state.
+  Flush(context);
+}
+
+// static
+void ConsoleInterceptor::Printf(InterceptorContext& context,
+                                const char* format,
+                                ...) {
+  auto& tls = context.GetThreadLocalState();
+  ssize_t remaining = static_cast<ssize_t>(tls.message_buffer.size()) -
+                      static_cast<ssize_t>(tls.buffer_pos);
+  int written = 0;
+  if (remaining > 0) {
+    va_list args;
+    va_start(args, format);
+    written = vsnprintf(&tls.message_buffer[tls.buffer_pos],
+                        static_cast<size_t>(remaining), format, args);
+    PERFETTO_DCHECK(written > 0);
+    va_end(args);
+  }
+
+  // In case of buffer overflow, flush to the fd and write the latest message to
+  // it directly instead.
+  if (remaining <= 0 || written > remaining) {
+    FILE* output = (tls.fd == STDOUT_FILENO) ? stdout : stderr;
+    if (g_output_fd_for_testing) {
+      output = fdopen(dup(g_output_fd_for_testing), "w");
+    }
+    Flush(context);
+    va_list args;
+    va_start(args, format);
+    vfprintf(output, format, args);
+    va_end(args);
+    if (g_output_fd_for_testing) {
+      fclose(output);
+    }
+  } else if (written > 0) {
+    tls.buffer_pos += static_cast<size_t>(written);
+  }
+}
+
+// static
+void ConsoleInterceptor::Flush(InterceptorContext& context) {
+  auto& tls = context.GetThreadLocalState();
+  ssize_t res = base::WriteAll(tls.fd, &tls.message_buffer[0], tls.buffer_pos);
+  PERFETTO_DCHECK(res == static_cast<ssize_t>(tls.buffer_pos));
+  tls.buffer_pos = 0;
+}
+
+// static
+void ConsoleInterceptor::SetColor(InterceptorContext& context,
+                                  const ConsoleColor& color) {
+  auto& tls = context.GetThreadLocalState();
+  if (!tls.use_colors)
+    return;
+  Printf(context, FMT_RGB_SET, color.r, color.g, color.b);
+}
+
+// static
+void ConsoleInterceptor::SetColor(InterceptorContext& context,
+                                  const char* color) {
+  auto& tls = context.GetThreadLocalState();
+  if (!tls.use_colors)
+    return;
+  Printf(context, "%s", color);
+}
+
+// static
+void ConsoleInterceptor::PrintAnnotations(
+    InterceptorContext& context,
+    const protos::pbzero::TrackEvent_Decoder& track_event,
+    const ConsoleColor& slice_color,
+    const ConsoleColor& highlight_color) {
+  auto& tls = context.GetThreadLocalState();
+
+  SetColor(context, slice_color);
+  Printf(context, "(");
+
+  bool is_first = true;
+  for (auto it = track_event.debug_annotations(); it; it++) {
+    perfetto::protos::pbzero::DebugAnnotation::Decoder annotation(*it);
+    protozero::ConstChars name{};
+    if (annotation.name_iid()) {
+      name.data =
+          tls.sequence_state.debug_annotation_names[annotation.name_iid()]
+              .data();
+      name.size =
+          tls.sequence_state.debug_annotation_names[annotation.name_iid()]
+              .size();
+    } else if (annotation.has_name()) {
+      name.data = annotation.name().data;
+      name.size = annotation.name().size;
+    }
+    SetColor(context, slice_color);
+    if (!is_first)
+      Printf(context, ", ");
+    Printf(context, "%.*s:", static_cast<int>(name.size), name.data);
+    SetColor(context, highlight_color);
+    if (annotation.has_bool_value()) {
+      Printf(context, "%s", annotation.bool_value() ? "true" : "false");
+    } else if (annotation.has_uint_value()) {
+      Printf(context, "%" PRIu64, annotation.uint_value());
+    } else if (annotation.has_int_value()) {
+      Printf(context, "%" PRId64, annotation.int_value());
+    } else if (annotation.has_double_value()) {
+      Printf(context, "%f", annotation.double_value());
+    } else if (annotation.has_string_value()) {
+      Printf(context, "%.*s", static_cast<int>(annotation.string_value().size),
+             annotation.string_value().data);
+    } else if (annotation.has_pointer_value()) {
+      Printf(context, "%p",
+             reinterpret_cast<void*>(annotation.pointer_value()));
+    } else if (annotation.has_nested_value()) {
+      perfetto::protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
+          annotation.nested_value());
+      PrintNestedValue(context, value);
+    } else if (annotation.has_legacy_json_value()) {
+      Printf(context, "%.*s",
+             static_cast<int>(annotation.legacy_json_value().size),
+             annotation.legacy_json_value().data);
+    }
+    is_first = false;
+  }
+  SetColor(context, slice_color);
+  Printf(context, ")");
+}
+
+// static
+void ConsoleInterceptor::PrintNestedValue(
+    InterceptorContext& context,
+    const perfetto::protos::pbzero::DebugAnnotation::NestedValue::Decoder&
+        value) {
+  if (value.nested_type() ==
+      protos::pbzero::DebugAnnotation::NestedValue::DICT) {
+    Printf(context, "{");
+    auto key_it = value.dict_keys();
+    auto value_it = value.dict_values();
+    bool is_first = true;
+    while (key_it && value_it) {
+      auto key = *key_it++;
+      if (!is_first)
+        Printf(context, ", ");
+      Printf(context, "%.*s:", static_cast<int>(key.size), key.data);
+      perfetto::protos::pbzero::DebugAnnotation::NestedValue::Decoder
+          dict_value(*value_it++);
+      PrintNestedValue(context, dict_value);
+      is_first = false;
+    }
+    Printf(context, "}");
+    return;
+  }
+  if (value.nested_type() ==
+      protos::pbzero::DebugAnnotation::NestedValue::ARRAY) {
+    Printf(context, "[");
+    bool is_first = true;
+    for (auto it = value.array_values(); it; it++) {
+      if (!is_first)
+        Printf(context, ", ");
+      perfetto::protos::pbzero::DebugAnnotation::NestedValue::Decoder
+          array_value(*it);
+      PrintNestedValue(context, array_value);
+      is_first = false;
+    }
+    Printf(context, "]");
+    return;
+  }
+  if (value.has_int_value()) {
+    Printf(context, "%" PRId64, value.int_value());
+  } else if (value.has_double_value()) {
+    Printf(context, "%f", value.double_value());
+  } else if (value.has_bool_value()) {
+    Printf(context, "%s", value.bool_value() ? "true" : "false");
+  } else if (value.has_string_value()) {
+    Printf(context, "%.*s", static_cast<int>(value.string_value().size),
+           value.string_value().data);
+  }
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/consumer_api_deprecated/consumer_api_deprecated.cc b/src/tracing/consumer_api_deprecated/consumer_api_deprecated.cc
index 8ef2660..6d901d8 100644
--- a/src/tracing/consumer_api_deprecated/consumer_api_deprecated.cc
+++ b/src/tracing/consumer_api_deprecated/consumer_api_deprecated.cc
@@ -87,7 +87,7 @@
   // perfetto::Consumer implementation.
   void OnConnect() override;
   void OnDisconnect() override;
-  void OnTracingDisabled() override;
+  void OnTracingDisabled(const std::string& error) override;
   void OnTraceData(std::vector<TracePacket>, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
@@ -198,9 +198,9 @@
   consumer_endpoint_->StartTracing();
 }
 
-void TracingSession::OnTracingDisabled() {
+void TracingSession::OnTracingDisabled(const std::string& error) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  PERFETTO_DLOG("OnTracingDisabled");
+  PERFETTO_DLOG("OnTracingDisabled %s", error.c_str());
 
   struct stat stat_buf {};
   int res = fstat(buf_fd_.get(), &stat_buf);
@@ -287,11 +287,11 @@
   void ThreadMain();  // Called on |task_runner_| thread.
 
   std::mutex mutex_;
-  std::thread thread_;
   std::unique_ptr<base::UnixTaskRunner> task_runner_;
   std::condition_variable task_runner_initialized_;
   Handle last_handle_ = 0;
   std::map<Handle, std::unique_ptr<TracingSession>> sessions_;
+  std::thread thread_;  // Keep last.
 };
 
 TracingController* TracingController::GetInstance() {
@@ -299,9 +299,9 @@
   return instance;
 }
 
-TracingController::TracingController()
-    : thread_(&TracingController::ThreadMain, this) {
+TracingController::TracingController() {
   std::unique_lock<std::mutex> lock(mutex_);
+  thread_ = std::thread(&TracingController::ThreadMain, this);
   task_runner_initialized_.wait(lock, [this] { return !!task_runner_; });
 }
 
diff --git a/src/tracing/core/id_allocator.cc b/src/tracing/core/id_allocator.cc
index 588e917..4b32473 100644
--- a/src/tracing/core/id_allocator.cc
+++ b/src/tracing/core/id_allocator.cc
@@ -56,4 +56,12 @@
   ids_[id] = false;
 }
 
+bool IdAllocatorGeneric::IsEmpty() const {
+  for (const auto id : ids_) {
+    if (id)
+      return false;
+  }
+  return true;
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/core/id_allocator.h b/src/tracing/core/id_allocator.h
index 7f912c3..c19d843 100644
--- a/src/tracing/core/id_allocator.h
+++ b/src/tracing/core/id_allocator.h
@@ -39,6 +39,8 @@
   uint32_t AllocateGeneric();
   void FreeGeneric(uint32_t);
 
+  bool IsEmpty() const;
+
  private:
   IdAllocatorGeneric(const IdAllocatorGeneric&) = delete;
   IdAllocatorGeneric& operator=(const IdAllocatorGeneric&) = delete;
diff --git a/src/tracing/core/null_trace_writer.cc b/src/tracing/core/null_trace_writer.cc
index 40fe9a5..3e7af89 100644
--- a/src/tracing/core/null_trace_writer.cc
+++ b/src/tracing/core/null_trace_writer.cc
@@ -18,16 +18,14 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
-
 #include "perfetto/protozero/message.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto {
 
-NullTraceWriter::NullTraceWriter()
-    : delegate_(base::kPageSize), stream_(&delegate_) {
-  cur_packet_.reset(new protos::pbzero::TracePacket());
+NullTraceWriter::NullTraceWriter() : delegate_(4096), stream_(&delegate_) {
+  cur_packet_.reset(new protozero::RootMessage<protos::pbzero::TracePacket>());
   cur_packet_->Finalize();  // To avoid the DCHECK in NewTracePacket().
 }
 
diff --git a/src/tracing/core/null_trace_writer.h b/src/tracing/core/null_trace_writer.h
index fabbca7..4f6c707 100644
--- a/src/tracing/core/null_trace_writer.h
+++ b/src/tracing/core/null_trace_writer.h
@@ -19,6 +19,7 @@
 
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_stream_null_delegate.h"
 
 namespace perfetto {
@@ -47,7 +48,8 @@
 
   // The packet returned via NewTracePacket(). Its owned by this class,
   // TracePacketHandle has just a pointer to it.
-  std::unique_ptr<protos::pbzero::TracePacket> cur_packet_;
+  std::unique_ptr<protozero::RootMessage<protos::pbzero::TracePacket>>
+      cur_packet_;
 };
 
 }  // namespace perfetto
diff --git a/src/tracing/core/null_trace_writer_unittest.cc b/src/tracing/core/null_trace_writer_unittest.cc
index 03374da..dfc3f47 100644
--- a/src/tracing/core/null_trace_writer_unittest.cc
+++ b/src/tracing/core/null_trace_writer_unittest.cc
@@ -33,7 +33,7 @@
 
 TEST(NullTraceWriterTest, Writing) {
   NullTraceWriter writer;
-  for (size_t i = 0; i < 3 * base::kPageSize; i++) {
+  for (size_t i = 0; i < 10000; i++) {
     auto packet = writer.NewTracePacket();
     packet->set_for_testing()->set_str("Hello, world!");
   }
diff --git a/src/tracing/core/shared_memory_abi.cc b/src/tracing/core/shared_memory_abi.cc
index 75b9713..9461848 100644
--- a/src/tracing/core/shared_memory_abi.cc
+++ b/src/tracing/core/shared_memory_abi.cc
@@ -73,6 +73,7 @@
 constexpr uint32_t SharedMemoryABI::kNumChunksForLayout[];
 constexpr const char* SharedMemoryABI::kChunkStateStr[];
 constexpr const size_t SharedMemoryABI::kInvalidPageIdx;
+constexpr const size_t SharedMemoryABI::kMinPageSize;
 constexpr const size_t SharedMemoryABI::kMaxPageSize;
 constexpr const size_t SharedMemoryABI::kPacketSizeDropPacket;
 
@@ -123,7 +124,7 @@
   static_assert((kAllChunksComplete & kChunkMask) == kChunkComplete,
                 "kAllChunksComplete out of sync with kChunkComplete");
 
-  // Sanity check the consistency of the kMax... constants.
+  // Check the consistency of the kMax... constants.
   static_assert(sizeof(ChunkHeader::writer_id) == sizeof(WriterID),
                 "WriterID size");
   ChunkHeader chunk_header{};
@@ -133,10 +134,10 @@
   chunk_header.writer_id.store(static_cast<uint16_t>(-1));
   PERFETTO_CHECK(kMaxWriterID <= chunk_header.writer_id.load());
 
-  PERFETTO_CHECK(page_size >= base::kPageSize);
+  PERFETTO_CHECK(page_size >= kMinPageSize);
   PERFETTO_CHECK(page_size <= kMaxPageSize);
-  PERFETTO_CHECK(page_size % base::kPageSize == 0);
-  PERFETTO_CHECK(reinterpret_cast<uintptr_t>(start) % base::kPageSize == 0);
+  PERFETTO_CHECK(page_size % kMinPageSize == 0);
+  PERFETTO_CHECK(reinterpret_cast<uintptr_t>(start) % kMinPageSize == 0);
   PERFETTO_CHECK(size % page_size == 0);
 }
 
diff --git a/src/tracing/core/shared_memory_abi_unittest.cc b/src/tracing/core/shared_memory_abi_unittest.cc
index aed8893..7069cd7 100644
--- a/src/tracing/core/shared_memory_abi_unittest.cc
+++ b/src/tracing/core/shared_memory_abi_unittest.cc
@@ -107,7 +107,7 @@
       ASSERT_EQ(SharedMemoryABI::kChunkBeingWritten,
                 abi.GetChunkState(page_idx, chunk_idx));
 
-      // Sanity check chunk bounds.
+      // Check chunk bounds.
       size_t expected_chunk_size =
           (page_size() - sizeof(SharedMemoryABI::PageHeader)) / num_chunks;
       expected_chunk_size = expected_chunk_size - (expected_chunk_size % 4);
@@ -142,6 +142,14 @@
           chunk.header()->packets.load().flags &
           SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk);
 
+      // Test clearing the needs patching flag.
+      chunk.SetFlag(SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+      ASSERT_TRUE(chunk.header()->packets.load().flags &
+                  SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+      chunk.ClearNeedsPatchingFlag();
+      ASSERT_FALSE(chunk.header()->packets.load().flags &
+                   SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+
       // Reacquiring the same chunk should fail.
       ASSERT_FALSE(abi.TryAcquireChunkForWriting(page_idx, chunk_idx, &header)
                        .is_valid());
diff --git a/src/tracing/core/shared_memory_arbiter_impl.cc b/src/tracing/core/shared_memory_arbiter_impl.cc
index c19c96a..6107efa 100644
--- a/src/tracing/core/shared_memory_arbiter_impl.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl.cc
@@ -249,7 +249,9 @@
     MaybeUnboundBufferID target_buffer,
     PatchList* patch_list) {
   // Note: chunk will be invalid if the call came from SendPatches().
-  base::TaskRunner* task_runner_to_post_callback_on = nullptr;
+  base::TaskRunner* task_runner_to_post_delayed_callback_on = nullptr;
+  // The delay with which the flush will be posted.
+  uint32_t flush_delay_ms = 0;
   base::WeakPtr<SharedMemoryArbiterImpl> weak_this;
   {
     std::lock_guard<std::mutex> scoped_lock(lock_);
@@ -259,9 +261,11 @@
 
       // Flushing the commit is only supported while we're |fully_bound_|. If we
       // aren't, we'll flush when |fully_bound_| is updated.
-      if (fully_bound_) {
+      if (fully_bound_ && !delayed_flush_scheduled_) {
         weak_this = weak_ptr_factory_.GetWeakPtr();
-        task_runner_to_post_callback_on = task_runner_;
+        task_runner_to_post_delayed_callback_on = task_runner_;
+        flush_delay_ms = batch_commits_duration_ms_;
+        delayed_flush_scheduled_ = true;
       }
     }
 
@@ -270,10 +274,31 @@
       PERFETTO_DCHECK(chunk.writer_id() == writer_id);
       uint8_t chunk_idx = chunk.chunk_idx();
       bytes_pending_commit_ += chunk.size();
-      size_t page_idx = shmem_abi_.ReleaseChunkAsComplete(std::move(chunk));
+      size_t page_idx;
+      // If the chunk needs patching, it should not be marked as complete yet,
+      // because this would indicate to the service that the producer will not
+      // be writing to it anymore, while the producer might still apply patches
+      // to the chunk later on. In particular, when re-reading (e.g. because of
+      // periodic scraping) a completed chunk, the service expects the flags of
+      // that chunk not to be removed between reads. So, let's say the producer
+      // marked the chunk as complete here and the service then read it for the
+      // first time. If the producer then fully patched the chunk, thus removing
+      // the kChunkNeedsPatching flag, and the service re-read the chunk after
+      // the patching, the service would be thrown off by the removed flag.
+      if (direct_patching_enabled_ &&
+          (chunk.GetPacketCountAndFlags().second &
+           SharedMemoryABI::ChunkHeader::kChunkNeedsPatching)) {
+        page_idx = shmem_abi_.GetPageAndChunkIndex(std::move(chunk)).first;
+      } else {
+        // If the chunk doesn't need patching, we can mark it as complete
+        // immediately. This allows the service to read it in full while
+        // scraping, which would not be the case if the chunk was left in a
+        // kChunkBeingWritten state.
+        page_idx = shmem_abi_.ReleaseChunkAsComplete(std::move(chunk));
+      }
 
-      // DO NOT access |chunk| after this point, has been std::move()-d above.
-
+      // DO NOT access |chunk| after this point, it has been std::move()-d
+      // above.
       CommitDataRequest::ChunksToMove* ctm =
           commit_data_req_->add_chunks_to_move();
       ctm->set_page(static_cast<uint32_t>(page_idx));
@@ -281,45 +306,174 @@
       ctm->set_target_buffer(target_buffer);
     }
 
-    // Get the completed patches for previous chunks from the |patch_list|
-    // and attach them.
-    ChunkID last_chunk_id = 0;  // 0 is irrelevant but keeps the compiler happy.
-    CommitDataRequest::ChunkToPatch* last_chunk_req = nullptr;
+    // Process the completed patches for previous chunks from the |patch_list|.
+    CommitDataRequest::ChunkToPatch* last_patch_req = nullptr;
     while (!patch_list->empty() && patch_list->front().is_patched()) {
-      if (!last_chunk_req || last_chunk_id != patch_list->front().chunk_id) {
-        last_chunk_req = commit_data_req_->add_chunks_to_patch();
-        last_chunk_req->set_writer_id(writer_id);
-        last_chunk_id = patch_list->front().chunk_id;
-        last_chunk_req->set_chunk_id(last_chunk_id);
-        last_chunk_req->set_target_buffer(target_buffer);
-      }
-      auto* patch_req = last_chunk_req->add_patches();
-      patch_req->set_offset(patch_list->front().offset);
-      patch_req->set_data(&patch_list->front().size_field[0],
-                          patch_list->front().size_field.size());
+      Patch curr_patch = patch_list->front();
       patch_list->pop_front();
+      // Patches for the same chunk are contiguous in the |patch_list|. So, to
+      // determine if there are any other patches that apply to the chunk that
+      // is being patched, check if the next patch in the |patch_list| applies
+      // to the same chunk.
+      bool chunk_needs_more_patching =
+          !patch_list->empty() &&
+          patch_list->front().chunk_id == curr_patch.chunk_id;
+
+      if (direct_patching_enabled_ &&
+          TryDirectPatchLocked(writer_id, curr_patch,
+                               chunk_needs_more_patching)) {
+        continue;
+      }
+
+      // The chunk that this patch applies to has already been released to the
+      // service, so it cannot be patches here. Add the patch to the commit data
+      // request, so that it can be sent to the service and applied there.
+      if (!last_patch_req ||
+          last_patch_req->chunk_id() != curr_patch.chunk_id) {
+        last_patch_req = commit_data_req_->add_chunks_to_patch();
+        last_patch_req->set_writer_id(writer_id);
+        last_patch_req->set_chunk_id(curr_patch.chunk_id);
+        last_patch_req->set_target_buffer(target_buffer);
+      }
+      auto* patch = last_patch_req->add_patches();
+      patch->set_offset(curr_patch.offset);
+      patch->set_data(&curr_patch.size_field[0], curr_patch.size_field.size());
     }
+
     // Patches are enqueued in the |patch_list| in order and are notified to
     // the service when the chunk is returned. The only case when the current
     // patch list is incomplete is if there is an unpatched entry at the head of
     // the |patch_list| that belongs to the same ChunkID as the last one we are
     // about to send to the service.
-    if (last_chunk_req && !patch_list->empty() &&
-        patch_list->front().chunk_id == last_chunk_id) {
-      last_chunk_req->set_has_more_patches(true);
+    if (last_patch_req && !patch_list->empty() &&
+        patch_list->front().chunk_id == last_patch_req->chunk_id()) {
+      last_patch_req->set_has_more_patches(true);
+    }
+
+    // If the buffer is filling up or if we are given a patch for a chunk
+    // that was already sent to the service, we don't want to wait for the next
+    // delayed flush to happen and we flush immediately. Otherwise, if we
+    // accumulate the patch and a crash occurs before the patch is sent, the
+    // service will not know of the patch and won't be able to reconstruct the
+    // trace.
+    if (fully_bound_ &&
+        (last_patch_req || bytes_pending_commit_ >= shmem_abi_.size() / 2)) {
+      weak_this = weak_ptr_factory_.GetWeakPtr();
+      task_runner_to_post_delayed_callback_on = task_runner_;
+      flush_delay_ms = 0;
     }
   }  // scoped_lock(lock_)
 
-  // We shouldn't post tasks while locked. |task_runner_to_post_callback_on|
-  // remains valid after unlocking, because |task_runner_| is never reset.
-  if (task_runner_to_post_callback_on) {
-    task_runner_to_post_callback_on->PostTask([weak_this] {
-      if (weak_this)
-        weak_this->FlushPendingCommitDataRequests();
-    });
+  // We shouldn't post tasks while locked.
+  // |task_runner_to_post_delayed_callback_on| remains valid after unlocking,
+  // because |task_runner_| is never reset.
+  if (task_runner_to_post_delayed_callback_on) {
+    task_runner_to_post_delayed_callback_on->PostDelayedTask(
+        [weak_this] {
+          if (!weak_this)
+            return;
+          {
+            std::lock_guard<std::mutex> scoped_lock(weak_this.get()->lock_);
+            // Clear |delayed_flush_scheduled_|, allowing the next call to
+            // UpdateCommitDataRequest to start another batching period.
+            weak_this.get()->delayed_flush_scheduled_ = false;
+          }
+          weak_this->FlushPendingCommitDataRequests();
+        },
+        flush_delay_ms);
   }
 }
 
+bool SharedMemoryArbiterImpl::TryDirectPatchLocked(
+    WriterID writer_id,
+    const Patch& patch,
+    bool chunk_needs_more_patching) {
+  // Search the chunks that are being batched in |commit_data_req_| for a chunk
+  // that needs patching and that matches the provided |writer_id| and
+  // |patch.chunk_id|. Iterate |commit_data_req_| in reverse, since
+  // |commit_data_req_| is appended to at the end with newly-returned chunks,
+  // and patches are more likely to apply to chunks that have been returned
+  // recently.
+  SharedMemoryABI::Chunk chunk;
+  bool chunk_found = false;
+  auto& chunks_to_move = commit_data_req_->chunks_to_move();
+  for (auto ctm_it = chunks_to_move.rbegin(); ctm_it != chunks_to_move.rend();
+       ++ctm_it) {
+    uint32_t layout = shmem_abi_.GetPageLayout(ctm_it->page());
+    auto chunk_state =
+        shmem_abi_.GetChunkStateFromLayout(layout, ctm_it->chunk());
+    // Note: the subset of |commit_data_req_| chunks that still need patching is
+    // also the subset of chunks that are still being written to. The rest of
+    // the chunks in |commit_data_req_| do not need patching and have already
+    // been marked as complete.
+    if (chunk_state != SharedMemoryABI::kChunkBeingWritten)
+      continue;
+
+    chunk =
+        shmem_abi_.GetChunkUnchecked(ctm_it->page(), layout, ctm_it->chunk());
+    if (chunk.writer_id() == writer_id &&
+        chunk.header()->chunk_id.load(std::memory_order_relaxed) ==
+            patch.chunk_id) {
+      chunk_found = true;
+      break;
+    }
+  }
+
+  if (!chunk_found) {
+    // The chunk has already been committed to the service and the patch cannot
+    // be applied in the producer.
+    return false;
+  }
+
+  // Apply the patch.
+  size_t page_idx;
+  uint8_t chunk_idx;
+  std::tie(page_idx, chunk_idx) = shmem_abi_.GetPageAndChunkIndex(chunk);
+  PERFETTO_DCHECK(shmem_abi_.GetChunkState(page_idx, chunk_idx) ==
+                  SharedMemoryABI::ChunkState::kChunkBeingWritten);
+  auto chunk_begin = chunk.payload_begin();
+  uint8_t* ptr = chunk_begin + patch.offset;
+  PERFETTO_CHECK(ptr <= chunk.end() - SharedMemoryABI::kPacketHeaderSize);
+  // DCHECK that we are writing into a zero-filled size field and not into
+  // valid data. It relies on ScatteredStreamWriter::ReserveBytes() to
+  // zero-fill reservations in debug builds.
+  const char zero[SharedMemoryABI::kPacketHeaderSize]{};
+  PERFETTO_DCHECK(memcmp(ptr, &zero, SharedMemoryABI::kPacketHeaderSize) == 0);
+
+  memcpy(ptr, &patch.size_field[0], SharedMemoryABI::kPacketHeaderSize);
+
+  if (!chunk_needs_more_patching) {
+    // Mark that the chunk doesn't need more patching and mark it as complete,
+    // as the producer will not write to it anymore. This allows the service to
+    // read the chunk in full while scraping, which would not be the case if the
+    // chunk was left in a kChunkBeingWritten state.
+    chunk.ClearNeedsPatchingFlag();
+    shmem_abi_.ReleaseChunkAsComplete(std::move(chunk));
+  }
+
+  return true;
+}
+
+void SharedMemoryArbiterImpl::SetBatchCommitsDuration(
+    uint32_t batch_commits_duration_ms) {
+  std::lock_guard<std::mutex> scoped_lock(lock_);
+  batch_commits_duration_ms_ = batch_commits_duration_ms;
+}
+
+bool SharedMemoryArbiterImpl::EnableDirectSMBPatching() {
+  std::lock_guard<std::mutex> scoped_lock(lock_);
+  if (!direct_patching_supported_by_service_) {
+    return false;
+  }
+
+  return direct_patching_enabled_ = true;
+}
+
+void SharedMemoryArbiterImpl::SetDirectSMBPatchingSupportedByService() {
+  std::lock_guard<std::mutex> scoped_lock(lock_);
+  direct_patching_supported_by_service_ = true;
+}
+
 // This function is quite subtle. When making changes keep in mind these two
 // challenges:
 // 1) If the producer stalls and we happen to be on the |task_runner_| IPC
@@ -370,6 +524,27 @@
       // should have been replaced.
       PERFETTO_DCHECK(all_placeholders_replaced);
 
+      // In order to allow patching in the producer we delay the kChunkComplete
+      // transition and keep batched chunks in the kChunkBeingWritten state.
+      // Since we are about to notify the service of all batched chunks, it will
+      // not be possible to apply any more patches to them and we need to move
+      // them to kChunkComplete - otherwise the service won't look at them.
+      for (auto& ctm : commit_data_req_->chunks_to_move()) {
+        uint32_t layout = shmem_abi_.GetPageLayout(ctm.page());
+        auto chunk_state =
+            shmem_abi_.GetChunkStateFromLayout(layout, ctm.chunk());
+        // Note: the subset of |commit_data_req_| chunks that still need
+        // patching is also the subset of chunks that are still being written
+        // to. The rest of the chunks in |commit_data_req_| do not need patching
+        // and have already been marked as complete.
+        if (chunk_state != SharedMemoryABI::kChunkBeingWritten)
+          continue;
+
+        SharedMemoryABI::Chunk chunk =
+            shmem_abi_.GetChunkUnchecked(ctm.page(), layout, ctm.chunk());
+        shmem_abi_.ReleaseChunkAsComplete(std::move(chunk));
+      }
+
       req = std::move(commit_data_req_);
       bytes_pending_commit_ = 0;
     }
@@ -386,6 +561,13 @@
   }
 }
 
+bool SharedMemoryArbiterImpl::TryShutdown() {
+  std::lock_guard<std::mutex> scoped_lock(lock_);
+  did_shutdown_ = true;
+  // Shutdown is safe if there are no active trace writers for this arbiter.
+  return active_writer_ids_.IsEmpty();
+}
+
 std::unique_ptr<TraceWriter> SharedMemoryArbiterImpl::CreateTraceWriter(
     BufferID target_buffer,
     BufferExhaustedPolicy buffer_exhausted_policy) {
@@ -609,8 +791,10 @@
 
   {
     std::lock_guard<std::mutex> scoped_lock(lock_);
-    id = active_writer_ids_.Allocate();
+    if (did_shutdown_)
+      return std::unique_ptr<TraceWriter>(new NullTraceWriter());
 
+    id = active_writer_ids_.Allocate();
     if (!id)
       return std::unique_ptr<TraceWriter>(new NullTraceWriter());
 
diff --git a/src/tracing/core/shared_memory_arbiter_impl.h b/src/tracing/core/shared_memory_arbiter_impl.h
index 766059c..3f679c4 100644
--- a/src/tracing/core/shared_memory_arbiter_impl.h
+++ b/src/tracing/core/shared_memory_arbiter_impl.h
@@ -35,6 +35,7 @@
 namespace perfetto {
 
 class PatchList;
+class Patch;
 class TraceWriter;
 class TraceWriterImpl;
 
@@ -126,10 +127,6 @@
                    MaybeUnboundBufferID target_buffer,
                    PatchList* patch_list);
 
-  // Forces a synchronous commit of the completed packets without waiting for
-  // the next task.
-  void FlushPendingCommitDataRequests(std::function<void()> callback = {});
-
   SharedMemoryABI* shmem_abi_for_testing() { return &shmem_abi_; }
 
   static void set_default_layout_for_testing(SharedMemoryABI::PageLayout l) {
@@ -151,6 +148,16 @@
       uint16_t target_buffer_reservation_id) override;
   void NotifyFlushComplete(FlushRequestID) override;
 
+  void SetBatchCommitsDuration(uint32_t batch_commits_duration_ms) override;
+
+  bool EnableDirectSMBPatching() override;
+
+  void SetDirectSMBPatchingSupportedByService() override;
+
+  void FlushPendingCommitDataRequests(
+      std::function<void()> callback = {}) override;
+  bool TryShutdown() override;
+
   base::TaskRunner* task_runner() const { return task_runner_; }
   size_t page_size() const { return shmem_abi_.page_size(); }
   size_t num_pages() const { return shmem_abi_.num_pages(); }
@@ -183,6 +190,18 @@
                                MaybeUnboundBufferID target_buffer,
                                PatchList* patch_list);
 
+  // Search the chunks that are being batched in |commit_data_req_| for a chunk
+  // that needs patching and that matches the provided |writer_id| and
+  // |patch.chunk_id|. If found, apply |patch| to that chunk, and if
+  // |chunk_needs_more_patching| is true, clear the needs patching flag of the
+  // chunk and mark it as complete - to allow the service to read it (and other
+  // chunks after it) during scraping. Returns true if the patch was applied,
+  // false otherwise.
+  //
+  // Note: the caller must be holding |lock_| for the duration of the call.
+  bool TryDirectPatchLocked(WriterID writer_id,
+                            const Patch& patch,
+                            bool chunk_needs_more_patching);
   std::unique_ptr<TraceWriter> CreateTraceWriterInternal(
       MaybeUnboundBufferID target_buffer,
       BufferExhaustedPolicy);
@@ -209,6 +228,7 @@
   bool UpdateFullyBoundLocked();
 
   const bool initially_bound_;
+
   // Only accessed on |task_runner_| after the producer endpoint was bound.
   TracingService::ProducerEndpoint* producer_endpoint_ = nullptr;
 
@@ -222,6 +242,7 @@
   std::unique_ptr<CommitDataRequest> commit_data_req_;
   size_t bytes_pending_commit_ = 0;  // SUM(chunk.size() : commit_data_req_).
   IdAllocator<WriterID> active_writer_ids_;
+  bool did_shutdown_ = false;
 
   // Whether the arbiter itself and all startup target buffer reservations are
   // bound. Note that this can become false again later if a new target buffer
@@ -238,6 +259,24 @@
   // reservation was unbound.
   std::vector<std::function<void()>> pending_flush_callbacks_;
 
+  // See SharedMemoryArbiter::SetBatchCommitsDuration.
+  uint32_t batch_commits_duration_ms_ = 0;
+
+  // See SharedMemoryArbiter::EnableDirectSMBPatching.
+  bool direct_patching_enabled_ = false;
+
+  // See SharedMemoryArbiter::SetDirectSMBPatchingSupportedByService.
+  bool direct_patching_supported_by_service_ = false;
+
+  // Indicates whether we have already scheduled a delayed flush for the
+  // purposes of batching. Set to true at the beginning of a batching period and
+  // cleared at the end of the period. Immediate flushes that happen during a
+  // batching period will empty the |commit_data_req| (triggering an immediate
+  // IPC to the service), but will not clear this flag and the
+  // previously-scheduled delayed flush will still occur at the end of the
+  // batching period.
+  bool delayed_flush_scheduled_ = false;
+
   // Stores target buffer reservations for writers created via
   // CreateStartupTraceWriter(). A bound reservation sets
   // TargetBufferReservation::resolved to true and is associated with the actual
diff --git a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
index 0326bcb..2b6da0a 100644
--- a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
@@ -35,8 +35,9 @@
 
 namespace perfetto {
 
-using testing::Invoke;
 using testing::_;
+using testing::Invoke;
+using testing::Mock;
 
 class MockProducerEndpoint : public TracingService::ProducerEndpoint {
  public:
@@ -146,6 +147,83 @@
   task_runner_->RunUntilCheckpoint("on_commit_2");
 }
 
+TEST_P(SharedMemoryArbiterImplTest, BatchCommits) {
+  SharedMemoryArbiterImpl::set_default_layout_for_testing(
+      SharedMemoryABI::PageLayout::kPageDiv1);
+
+  // Batching period is 0s - chunks are being committed as soon as they are
+  // returned.
+  SharedMemoryABI::Chunk chunk =
+      arbiter_->GetNewChunk({}, BufferExhaustedPolicy::kDefault);
+  ASSERT_TRUE(chunk.is_valid());
+  EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _)).Times(1);
+  PatchList ignored;
+  arbiter_->ReturnCompletedChunk(std::move(chunk), 0, &ignored);
+  task_runner_->RunUntilIdle();
+  ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock_producer_endpoint_));
+
+  // Since we cannot explicitly control the passage of time in task_runner_, to
+  // simulate a non-zero batching period and a commit at the end of it, set the
+  // batching duration to a very large value and call
+  // FlushPendingCommitDataRequests to manually trigger the commit.
+  arbiter_->SetDirectSMBPatchingSupportedByService();
+  ASSERT_TRUE(arbiter_->EnableDirectSMBPatching());
+  arbiter_->SetBatchCommitsDuration(UINT32_MAX);
+
+  // First chunk that will be batched. CommitData should not be called
+  // immediately this time.
+  chunk = arbiter_->GetNewChunk({}, BufferExhaustedPolicy::kDefault);
+  ASSERT_TRUE(chunk.is_valid());
+  EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _)).Times(0);
+  // We'll pretend that the chunk needs patching. This is done in order to
+  // verify that chunks that need patching are not marked as complete (i.e. they
+  // are kept in state kChunkBeingWritten) before the batching period ends - in
+  // case a patch for them arrives during the batching period.
+  chunk.SetFlag(SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+  arbiter_->ReturnCompletedChunk(std::move(chunk), 1, &ignored);
+  task_runner_->RunUntilIdle();
+  ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock_producer_endpoint_));
+  ASSERT_EQ(SharedMemoryABI::kChunkBeingWritten,
+            arbiter_->shmem_abi_for_testing()->GetChunkState(1u, 0u));
+
+  // Add a second chunk to the batch. This should also not trigger an immediate
+  // call to CommitData.
+  chunk = arbiter_->GetNewChunk({}, BufferExhaustedPolicy::kDefault);
+  ASSERT_TRUE(chunk.is_valid());
+  EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _)).Times(0);
+  arbiter_->ReturnCompletedChunk(std::move(chunk), 2, &ignored);
+  task_runner_->RunUntilIdle();
+  ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock_producer_endpoint_));
+  // This chunk does not need patching, so it should be marked as complete even
+  // before the end of the batching period - to allow the service to read it in
+  // full.
+  ASSERT_EQ(SharedMemoryABI::kChunkComplete,
+            arbiter_->shmem_abi_for_testing()->GetChunkState(2u, 0u));
+
+  // Make sure that CommitData gets called once (should happen at the end
+  // of the batching period), with the two chunks in the batch.
+  EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _))
+      .WillOnce(Invoke([](const CommitDataRequest& req,
+                          MockProducerEndpoint::CommitDataCallback) {
+        ASSERT_EQ(2, req.chunks_to_move_size());
+
+        // Verify that this is the first chunk that we expect to have been
+        // batched.
+        ASSERT_EQ(1u, req.chunks_to_move()[0].page());
+        ASSERT_EQ(0u, req.chunks_to_move()[0].chunk());
+        ASSERT_EQ(1u, req.chunks_to_move()[0].target_buffer());
+
+        // Verify that this is the second chunk that we expect to have been
+        // batched.
+        ASSERT_EQ(2u, req.chunks_to_move()[1].page());
+        ASSERT_EQ(0u, req.chunks_to_move()[1].chunk());
+        ASSERT_EQ(2u, req.chunks_to_move()[1].target_buffer());
+      }));
+
+  // Pretend we've reached the end of the batching period.
+  arbiter_->FlushPendingCommitDataRequests();
+}
+
 // Helper for verifying trace writer id allocations.
 class TraceWriterIdChecker : public FakeProducerEndpoint {
  public:
@@ -212,6 +290,22 @@
   EXPECT_TRUE(id_checking_endpoint.unregistered_ids_.all());
 }
 
+TEST_P(SharedMemoryArbiterImplTest, Shutdown) {
+  std::unique_ptr<TraceWriter> writer = arbiter_->CreateTraceWriter(1);
+  EXPECT_TRUE(writer);
+  EXPECT_FALSE(arbiter_->TryShutdown());
+
+  // We still get a valid trace writer after shutdown, but it's a null one
+  // that's not connected to the arbiter.
+  std::unique_ptr<TraceWriter> writer2 = arbiter_->CreateTraceWriter(2);
+  EXPECT_TRUE(writer2);
+  EXPECT_EQ(writer2->writer_id(), 0);
+
+  // Shutdown will succeed once the only non-null writer goes away.
+  writer.reset();
+  EXPECT_TRUE(arbiter_->TryShutdown());
+}
+
 // Verify that getting a new chunk doesn't stall when kDrop policy is chosen.
 TEST_P(SharedMemoryArbiterImplTest, BufferExhaustedPolicyDrop) {
   // Grab all chunks in the SMB.
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index 2227870..ce6fa34 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -84,9 +84,8 @@
 
 bool TraceBuffer::Initialize(size_t size) {
   static_assert(
-      base::kPageSize % sizeof(ChunkRecord) == 0,
+      SharedMemoryABI::kMinPageSize % sizeof(ChunkRecord) == 0,
       "sizeof(ChunkRecord) must be an integer divider of a page size");
-  PERFETTO_CHECK(size % base::kPageSize == 0);
   data_ = base::PagedMemory::Allocate(
       size, base::PagedMemory::kMayFail | base::PagedMemory::kDontCommit);
   if (!data_.IsValid()) {
@@ -121,7 +120,7 @@
       base::AlignUp<sizeof(ChunkRecord)>(size + sizeof(ChunkRecord));
   if (PERFETTO_UNLIKELY(record_size > max_chunk_size_)) {
     stats_.set_abi_violations(stats_.abi_violations() + 1);
-    PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+    PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
     return;
   }
 
@@ -172,7 +171,18 @@
                           prev->num_fragments > num_fragments ||
                           (prev->flags & chunk_flags) != prev->flags)) {
       stats_.set_abi_violations(stats_.abi_violations() + 1);
-      PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+      PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
+      return;
+    }
+
+    // If this chunk was previously copied with the same number of fragments and
+    // the number didn't change, there's no need to copy it again. If the
+    // previous chunk was complete already, this should always be the case.
+    PERFETTO_DCHECK(suppress_client_dchecks_for_testing_ ||
+                    !record_meta->is_complete() ||
+                    (chunk_complete && prev->num_fragments == num_fragments));
+    if (prev->num_fragments == num_fragments) {
+      TRACE_BUFFER_DLOG("  skipping recommit of identical chunk");
       return;
     }
 
@@ -189,18 +199,7 @@
     if (subsequent_it != index_.end() &&
         subsequent_it->second.num_fragments_read > 0) {
       stats_.set_abi_violations(stats_.abi_violations() + 1);
-      PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
-      return;
-    }
-
-    // If this chunk was previously copied with the same number of fragments and
-    // the number didn't change, there's no need to copy it again. If the
-    // previous chunk was complete already, this should always be the case.
-    PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_ ||
-                    !record_meta->is_complete() ||
-                    (chunk_complete && prev->num_fragments == num_fragments));
-    if (prev->num_fragments == num_fragments) {
-      TRACE_BUFFER_DLOG("  skipping recommit of identical chunk");
+      PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
       return;
     }
 
@@ -208,7 +207,7 @@
     if (record_meta->num_fragments_read > prev->num_fragments) {
       PERFETTO_ELOG(
           "TraceBuffer read too many fragments from an incomplete chunk");
-      PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+      PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
       return;
     }
 
@@ -826,7 +825,7 @@
     // The producer has a bug or is malicious and did declare that the chunk
     // contains more packets beyond its boundaries.
     stats_.set_abi_violations(stats_.abi_violations() + 1);
-    PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+    PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
     chunk_meta->cur_fragment_offset = 0;
     chunk_meta->num_fragments_read = chunk_meta->num_fragments;
     if (PERFETTO_LIKELY(chunk_meta->is_complete())) {
@@ -856,7 +855,7 @@
     // R).
     if (packet_size != SharedMemoryABI::kPacketSizeDropPacket) {
       stats_.set_abi_violations(stats_.abi_violations() + 1);
-      PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+      PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
     } else {
       stats_.set_trace_writer_packet_loss(stats_.trace_writer_packet_loss() +
                                           1);
@@ -884,7 +883,7 @@
     // We have at least one more packet to parse. It should be within the chunk.
     if (chunk_meta->cur_fragment_offset + sizeof(ChunkRecord) >=
         chunk_meta->chunk_record->size) {
-      PERFETTO_DCHECK(suppress_sanity_dchecks_for_testing_);
+      PERFETTO_DCHECK(suppress_client_dchecks_for_testing_);
     }
   }
 
diff --git a/src/tracing/core/trace_buffer.h b/src/tracing/core/trace_buffer.h
index a95c2f6..5196623 100644
--- a/src/tracing/core/trace_buffer.h
+++ b/src/tracing/core/trace_buffer.h
@@ -204,6 +204,14 @@
   // batch of patches for the chunk or there is more.
   // If |other_patches_pending| == false, the chunk is marked as ready to be
   // consumed. If true, the state of the chunk is not altered.
+  //
+  // Note: If the producer is batching commits (see shared_memory_arbiter.h), it
+  // will also attempt to do patching locally. Namely, if nested messages are
+  // completed while the chunk on which they started is being batched (i.e.
+  // before it has been committed to the service), the producer will apply the
+  // respective patches to the batched chunk. These patches will not be sent to
+  // the service - i.e. only the patches that the producer did not manage to
+  // apply before committing the chunk will be applied here.
   bool TryPatchChunkContents(ProducerID,
                              WriterID,
                              ChunkID,
@@ -649,7 +657,7 @@
   // When true disable some DCHECKs that have been put in place to detect
   // bugs in the producers. This is for tests that feed malicious inputs and
   // hence mimic a buggy producer.
-  bool suppress_sanity_dchecks_for_testing_ = false;
+  bool suppress_client_dchecks_for_testing_ = false;
 };
 
 }  // namespace perfetto
diff --git a/src/tracing/core/trace_buffer_unittest.cc b/src/tracing/core/trace_buffer_unittest.cc
index f7012a5..a7bf27a 100644
--- a/src/tracing/core/trace_buffer_unittest.cc
+++ b/src/tracing/core/trace_buffer_unittest.cc
@@ -124,8 +124,8 @@
         trace_buffer_->index_.lower_bound(key));
   }
 
-  void SuppressSanityDchecksForTesting() {
-    trace_buffer_->suppress_sanity_dchecks_for_testing_ = true;
+  void SuppressClientDchecksForTesting() {
+    trace_buffer_->suppress_client_dchecks_for_testing_ = true;
   }
 
   std::vector<ChunkMetaKey> GetIndex() {
@@ -978,7 +978,7 @@
 
 TEST_F(TraceBufferTest, Malicious_ZeroSizedChunk) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(32, 'a')
       .CopyIntoTraceBuffer();
@@ -1002,7 +1002,7 @@
 // in a no-op.
 TEST_F(TraceBufferTest, Malicious_ChunkTooBig) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(4096, 'a')
       .AddPacket(2048, 'b')
@@ -1013,7 +1013,7 @@
 
 TEST_F(TraceBufferTest, Malicious_DeclareMorePacketsBeyondBoundaries) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(64, 'a')
       .IncrementNumPackets()
@@ -1034,7 +1034,7 @@
 
 TEST_F(TraceBufferTest, Malicious_ZeroVarintHeader) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   // Create a standalone chunk where the varint header is == 0.
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(4, 'a')
@@ -1054,7 +1054,7 @@
 // end of the buffer).
 TEST_F(TraceBufferTest, Malicious_OverflowingVarintHeader) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(4079, 'a')  // 4079 := 4096 - sizeof(ChunkRecord) - 1
       .AddPacket({0x82})  // 0x8*: that the varint continues on the next byte.
@@ -1067,7 +1067,7 @@
 
 TEST_F(TraceBufferTest, Malicious_VarintHeaderTooBig) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
 
   // Add a valid chunk.
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
@@ -1108,7 +1108,7 @@
 // contains an enormous varint number that tries to overflow.
 TEST_F(TraceBufferTest, Malicious_JumboVarint) {
   ResetBuffer(64 * 1024);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
 
   std::vector<uint8_t> chunk;
   chunk.insert(chunk.end(), 64 * 1024 - sizeof(ChunkRecord) * 2, 0xff);
@@ -1128,7 +1128,7 @@
 // skipped.
 TEST_F(TraceBufferTest, Malicious_ZeroVarintHeaderInSequence) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(4, 'a', kContOnNextChunk)
       .CopyIntoTraceBuffer();
@@ -1161,7 +1161,7 @@
 // zero-sized fragment should be skipped.
 TEST_F(TraceBufferTest, Malicious_ZeroVarintHeaderAtEndOfChunk) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(4, 'a')
       .AddPacket(4, 'b', kContOnNextChunk)
@@ -1206,7 +1206,7 @@
 
 TEST_F(TraceBufferTest, Malicious_OverrideWithShorterChunkSize) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(2048, 'a')
       .CopyIntoTraceBuffer();
@@ -1222,7 +1222,7 @@
 
 TEST_F(TraceBufferTest, Malicious_OverrideWithShorterChunkSizeAfterRead) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
 
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(30, 'a')
@@ -1256,7 +1256,7 @@
 
 TEST_F(TraceBufferTest, Malicious_OverrideWithDifferentOffsetAfterRead) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
 
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(30, 'a')
@@ -1433,7 +1433,7 @@
 
   // Overriding a complete packet here would trigger a DCHECK because the packet
   // was already marked as complete.
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(20, 'a')
       .AddPacket(30, 'b')
@@ -1450,7 +1450,7 @@
 // See also the Malicious_Override* tests above.
 TEST_F(TraceBufferTest, Override_ReCommitInvalid) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(20, 'a')
       .AddPacket(30, 'b')
@@ -1738,7 +1738,7 @@
 
 TEST_F(TraceBufferTest, MissingPacketsOnSequence) {
   ResetBuffer(4096);
-  SuppressSanityDchecksForTesting();
+  SuppressClientDchecksForTesting();
   CreateChunk(ProducerID(1), WriterID(1), ChunkID(0))
       .AddPacket(10, 'a')
       .AddPacket(10, 'b')
diff --git a/src/tracing/core/trace_writer_for_testing.cc b/src/tracing/core/trace_writer_for_testing.cc
index 838f93e..c308394 100644
--- a/src/tracing/core/trace_writer_for_testing.cc
+++ b/src/tracing/core/trace_writer_for_testing.cc
@@ -25,11 +25,9 @@
 namespace perfetto {
 
 TraceWriterForTesting::TraceWriterForTesting()
-    : delegate_(static_cast<size_t>(base::kPageSize),
-                static_cast<size_t>(base::kPageSize)),
-      stream_(&delegate_) {
+    : delegate_(4096, 4096), stream_(&delegate_) {
   delegate_.set_writer(&stream_);
-  cur_packet_.reset(new protos::pbzero::TracePacket());
+  cur_packet_.reset(new protozero::RootMessage<protos::pbzero::TracePacket>());
   cur_packet_->Finalize();  // To avoid the DCHECK in NewTracePacket().
 }
 
diff --git a/src/tracing/core/trace_writer_for_testing.h b/src/tracing/core/trace_writer_for_testing.h
index f066fee..16c7c76 100644
--- a/src/tracing/core/trace_writer_for_testing.h
+++ b/src/tracing/core/trace_writer_for_testing.h
@@ -20,6 +20,7 @@
 
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 
@@ -53,7 +54,8 @@
 
   // The packet returned via NewTracePacket(). Its owned by this class,
   // TracePacketHandle has just a pointer to it.
-  std::unique_ptr<protos::pbzero::TracePacket> cur_packet_;
+  std::unique_ptr<protozero::RootMessage<protos::pbzero::TracePacket>>
+      cur_packet_;
 };
 
 }  // namespace perfetto
diff --git a/src/tracing/core/trace_writer_impl.cc b/src/tracing/core/trace_writer_impl.cc
index f0705ae..d9ddfc9 100644
--- a/src/tracing/core/trace_writer_impl.cc
+++ b/src/tracing/core/trace_writer_impl.cc
@@ -24,7 +24,9 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/thread_annotations.h"
+#include "perfetto/protozero/message.h"
 #include "perfetto/protozero/proto_utils.h"
+#include "perfetto/protozero/root_message.h"
 #include "src/tracing/core/shared_memory_arbiter_impl.h"
 
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
@@ -54,7 +56,7 @@
   // more gracefully and always return a no-op TracePacket in NewTracePacket().
   PERFETTO_CHECK(id_ != 0);
 
-  cur_packet_.reset(new protos::pbzero::TracePacket());
+  cur_packet_.reset(new protozero::RootMessage<protos::pbzero::TracePacket>());
   cur_packet_->Finalize();  // To avoid the DCHECK in NewTracePacket().
 }
 
@@ -63,6 +65,9 @@
     cur_packet_->Finalize();
     Flush();
   }
+  // This call may cause the shared memory arbiter (and the underlying memory)
+  // to get asynchronously deleted if this was the last trace writer targeting
+  // the arbiter and the arbiter was marked for shutdown.
   shmem_arbiter_->ReleaseWriterID(id_);
 }
 
diff --git a/src/tracing/core/trace_writer_impl.h b/src/tracing/core/trace_writer_impl.h
index be2daef..9f3b970 100644
--- a/src/tracing/core/trace_writer_impl.h
+++ b/src/tracing/core/trace_writer_impl.h
@@ -24,6 +24,7 @@
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/protozero/message_handle.h"
 #include "perfetto/protozero/proto_utils.h"
+#include "perfetto/protozero/root_message.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
 #include "perfetto/tracing/buffer_exhausted_policy.h"
 #include "src/tracing/core/patch_list.h"
@@ -92,7 +93,8 @@
 
   // The packet returned via NewTracePacket(). Its owned by this class,
   // TracePacketHandle has just a pointer to it.
-  std::unique_ptr<protos::pbzero::TracePacket> cur_packet_;
+  std::unique_ptr<protozero::RootMessage<protos::pbzero::TracePacket>>
+      cur_packet_;
 
   // The start address of |cur_packet_| within |cur_chunk_|. Used to figure out
   // fragments sizes when a TracePacket write is interrupted by GetNewBuffer().
diff --git a/src/tracing/core/trace_writer_impl_unittest.cc b/src/tracing/core/trace_writer_impl_unittest.cc
index b40a172..b658aa6 100644
--- a/src/tracing/core/trace_writer_impl_unittest.cc
+++ b/src/tracing/core/trace_writer_impl_unittest.cc
@@ -95,11 +95,12 @@
   // TODO(primiano): check also the content of the packets decoding the protos.
 }
 
-TEST_P(TraceWriterImplTest, FragmentingPacket) {
+TEST_P(TraceWriterImplTest, FragmentingPacketWithProducerAndServicePatching) {
   const BufferID kBufId = 42;
   std::unique_ptr<TraceWriter> writer = arbiter_->CreateTraceWriter(kBufId);
 
-  // Write a packet that's guaranteed to span more than a single chunk.
+  // Write a packet that's guaranteed to span more than a single chunk, but less
+  // than two chunks.
   auto packet = writer->NewTracePacket();
   size_t chunk_size = page_size() / 4;
   std::stringstream large_string_writer;
@@ -117,6 +118,30 @@
   EXPECT_EQ(kBufId, last_commit.chunks_to_move()[0].target_buffer());
   EXPECT_EQ(0, last_commit.chunks_to_patch_size());
 
+  // We will simulate a batching cycle by first setting the batching period to a
+  // very large value and then force-flushing when we are done writing data.
+  arbiter_->SetDirectSMBPatchingSupportedByService();
+  ASSERT_TRUE(arbiter_->EnableDirectSMBPatching());
+  arbiter_->SetBatchCommitsDuration(UINT32_MAX);
+
+  // Write a second packet that's guaranteed to span more than a single chunk.
+  // Starting a new trace packet should cause the patches for the first packet
+  // (i.e. for the first chunk) to be queued for sending to the service. They
+  // cannot be applied locally because the first chunk was already committed.
+  packet->Finalize();
+  auto packet2 = writer->NewTracePacket();
+  packet2->set_for_testing()->set_str(large_string.data(), large_string.size());
+
+  // Starting a new packet yet again should cause the patches for the second
+  // packet (i.e. for the second chunk) to be applied in the producer, because
+  // the second chunk has not been committed yet.
+  packet2->Finalize();
+  auto packet3 = writer->NewTracePacket();
+
+  // Simulate the end of the batching period, which should trigger a commit to
+  // the service.
+  arbiter_->FlushPendingCommitDataRequests();
+
   SharedMemoryABI* abi = arbiter_->shmem_abi_for_testing();
 
   // The first allocated chunk should be complete but need patching, since the
@@ -131,11 +156,80 @@
   ASSERT_TRUE(chunk.header()->packets.load().flags &
               SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk);
 
-  // Starting a new packet should cause patches to be applied.
+  // Verify that a patch for the first chunk was sent to the service.
+  ASSERT_EQ(1, last_commit.chunks_to_patch_size());
+  EXPECT_EQ(writer->writer_id(), last_commit.chunks_to_patch()[0].writer_id());
+  EXPECT_EQ(kBufId, last_commit.chunks_to_patch()[0].target_buffer());
+  EXPECT_EQ(chunk.header()->chunk_id.load(),
+            last_commit.chunks_to_patch()[0].chunk_id());
+  EXPECT_FALSE(last_commit.chunks_to_patch()[0].has_more_patches());
+  ASSERT_EQ(1, last_commit.chunks_to_patch()[0].patches_size());
+
+  // Verify that the second chunk was committed.
+  ASSERT_EQ(1, last_commit.chunks_to_move_size());
+  EXPECT_EQ(0u, last_commit.chunks_to_move()[0].page());
+  EXPECT_EQ(1u, last_commit.chunks_to_move()[0].chunk());
+  EXPECT_EQ(kBufId, last_commit.chunks_to_move()[0].target_buffer());
+
+  // The second chunk should be in a complete state and should not need
+  // patching, as the patches to it should have been applied in the producer.
+  ASSERT_EQ(SharedMemoryABI::kChunkComplete, abi->GetChunkState(0u, 1u));
+  auto chunk2 = abi->TryAcquireChunkForReading(0u, 1u);
+  ASSERT_TRUE(chunk2.is_valid());
+  ASSERT_EQ(2, chunk2.header()->packets.load().count);
+  ASSERT_TRUE(chunk2.header()->packets.load().flags &
+              SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk);
+  ASSERT_FALSE(chunk2.header()->packets.load().flags &
+               SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+}
+
+TEST_P(TraceWriterImplTest, FragmentingPacketWithoutEnablingProducerPatching) {
+  // We will simulate a batching cycle by first setting the batching period to a
+  // very large value and will force flush to simulate a flush happening when we
+  // believe it should - in this case when a patch is encountered.
+  //
+  // Note: direct producer-side patching should be disabled by default.
+  arbiter_->SetBatchCommitsDuration(UINT32_MAX);
+
+  const BufferID kBufId = 42;
+  std::unique_ptr<TraceWriter> writer = arbiter_->CreateTraceWriter(kBufId);
+
+  // Write a packet that's guaranteed to span more than a single chunk.
+  auto packet = writer->NewTracePacket();
+  size_t chunk_size = page_size() / 4;
+  std::stringstream large_string_writer;
+  for (size_t pos = 0; pos < chunk_size; pos++)
+    large_string_writer << "x";
+  std::string large_string = large_string_writer.str();
+  packet->set_for_testing()->set_str(large_string.data(), large_string.size());
+
+  // Starting a new packet should cause the first chunk and its patches to be
+  // committed to the service.
   packet->Finalize();
   auto packet2 = writer->NewTracePacket();
   arbiter_->FlushPendingCommitDataRequests();
-  EXPECT_EQ(0, last_commit.chunks_to_move_size());
+
+  // The first allocated chunk should be complete but need patching, since the
+  // packet extended past the chunk and no patches for the packet size or string
+  // field size were applied in the producer.
+  SharedMemoryABI* abi = arbiter_->shmem_abi_for_testing();
+  ASSERT_EQ(SharedMemoryABI::kChunkComplete, abi->GetChunkState(0u, 0u));
+  auto chunk = abi->TryAcquireChunkForReading(0u, 0u);
+  ASSERT_TRUE(chunk.is_valid());
+  ASSERT_EQ(1, chunk.header()->packets.load().count);
+  ASSERT_TRUE(chunk.header()->packets.load().flags &
+              SharedMemoryABI::ChunkHeader::kChunkNeedsPatching);
+  ASSERT_TRUE(chunk.header()->packets.load().flags &
+              SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk);
+
+  // The first chunk was committed.
+  const auto& last_commit = fake_producer_endpoint_.last_commit_data_request;
+  ASSERT_EQ(1, last_commit.chunks_to_move_size());
+  EXPECT_EQ(0u, last_commit.chunks_to_move()[0].page());
+  EXPECT_EQ(0u, last_commit.chunks_to_move()[0].chunk());
+  EXPECT_EQ(kBufId, last_commit.chunks_to_move()[0].target_buffer());
+
+  // The patches for the first chunk were committed.
   ASSERT_EQ(1, last_commit.chunks_to_patch_size());
   EXPECT_EQ(writer->writer_id(), last_commit.chunks_to_patch()[0].writer_id());
   EXPECT_EQ(kBufId, last_commit.chunks_to_patch()[0].target_buffer());
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 492c280..8644a81 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -17,6 +17,8 @@
 #include "src/tracing/core/tracing_service_impl.h"
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
 
 #include <errno.h>
 #include <inttypes.h>
@@ -38,7 +40,7 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
     PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
 #define PERFETTO_HAS_CHMOD
 #include <sys/stat.h>
 #endif
@@ -83,8 +85,19 @@
 // from the IPC layer, but we should never assume that that the producer calls
 // come in the right order or their arguments are sane / within bounds.
 
+// This is a macro because we want the call-site line number for the ELOG.
+#define PERFETTO_SVC_ERR(...) \
+  (PERFETTO_ELOG(__VA_ARGS__), ::perfetto::base::ErrStatus(__VA_ARGS__))
+
 namespace perfetto {
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+const char* kBugreportTracePath =
+    "/data/misc/perfetto-traces/bugreport.pftrace";
+#else
+const char* kBugreportTracePath = "/tmp/bugreport.pftrace";
+#endif
+
 namespace {
 constexpr int kMaxBuffersPerConsumer = 128;
 constexpr uint32_t kDefaultSnapshotsIntervalMs = 10 * 1000;
@@ -183,12 +196,17 @@
   page_size = std::min<size_t>(page_size, kMaxPageSize);
   shm_size = std::min<size_t>(shm_size, TracingServiceImpl::kMaxShmSize);
 
-  // Page size has to be multiple of system's page size.
-  bool page_size_is_valid = page_size >= base::kPageSize;
-  page_size_is_valid &= page_size % base::kPageSize == 0;
+  // The tracing page size has to be multiple of 4K. On some systems (e.g. Mac
+  // on Arm64) the system page size can be larger (e.g., 16K). That doesn't
+  // matter here, because the tracing page size is just a logical partitioning
+  // and does not have any dependencies on kernel mm syscalls (read: it's fine
+  // to have trace page sizes of 4K on a system where the kernel page size is
+  // 16K).
+  bool page_size_is_valid = page_size >= SharedMemoryABI::kMinPageSize;
+  page_size_is_valid &= page_size % SharedMemoryABI::kMinPageSize == 0;
 
   // Only allow power of two numbers of pages, i.e. 1, 2, 4, 8 pages.
-  size_t num_pages = page_size / base::kPageSize;
+  size_t num_pages = page_size / SharedMemoryABI::kMinPageSize;
   page_size_is_valid &= (num_pages & (num_pages - 1)) == 0;
 
   if (!page_size_is_valid || shm_size < page_size ||
@@ -216,8 +234,10 @@
   return filter_matches || filter_regex_matches;
 }
 
-// Used when write_into_file == true and output_path is not empty.
-base::ScopedFile CreateTraceFile(const std::string& path) {
+// Used when:
+// 1. TraceConfig.write_into_file == true and output_path is not empty.
+// 2. Calling SaveTraceForBugreport(), from perfetto --save-for-bugreport.
+base::ScopedFile CreateTraceFile(const std::string& path, bool overwrite) {
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
   static const char kBase[] = "/data/misc/perfetto-traces/";
   if (!base::StartsWith(path, kBase) || path.rfind('/') != strlen(kBase) - 1) {
@@ -227,16 +247,23 @@
   }
 #endif
   // O_CREAT | O_EXCL will fail if the file exists already.
-  auto fd = base::OpenFile(path, O_RDWR | O_CREAT | O_EXCL, 0600);
-  if (!fd)
-    PERFETTO_PLOG("Failed to create %s", path.c_str());
+  const int flags = O_RDWR | O_CREAT | (overwrite ? O_TRUNC : O_EXCL);
+  auto fd = base::OpenFile(path, flags, 0600);
+  if (fd) {
 #if defined(PERFETTO_HAS_CHMOD)
-  // Passing 0644 directly above won't work because of umask.
-  PERFETTO_CHECK(fchmod(*fd, 0644) == 0);
+    // Passing 0644 directly above won't work because of umask.
+    PERFETTO_CHECK(fchmod(*fd, 0644) == 0);
 #endif
+  } else {
+    PERFETTO_PLOG("Failed to create %s", path.c_str());
+  }
   return fd;
 }
 
+std::string GetBugreportTmpPath() {
+  return std::string(kBugreportTracePath) + ".tmp";
+}
+
 }  // namespace
 
 // These constants instead are defined in the header because are used by tests.
@@ -316,7 +343,11 @@
 
   // Producer::OnConnect() should run before Producer::OnTracingSetup(). The
   // latter may be posted by SetupSharedMemory() below, so post OnConnect() now.
-  task_runner_->PostTask(std::bind(&Producer::OnConnect, endpoint->producer_));
+  auto weak_ptr = endpoint->weak_ptr_factory_.GetWeakPtr();
+  task_runner_->PostTask([weak_ptr] {
+    if (weak_ptr)
+      weak_ptr->producer_->OnConnect();
+  });
 
   if (shm) {
     // The producer supplied an SMB. This is used only by Chrome; in the most
@@ -391,12 +422,10 @@
   PERFETTO_DCHECK(it_and_inserted.second);
   // Consumer might go away before we're able to send the connect notification,
   // if that is the case just bail out.
-  auto weak_ptr = endpoint->GetWeakPtr();
+  auto weak_ptr = endpoint->weak_ptr_factory_.GetWeakPtr();
   task_runner_->PostTask([weak_ptr] {
-    if (!weak_ptr) {
-      return;
-    }
-    weak_ptr->consumer_->OnConnect();
+    if (weak_ptr)
+      weak_ptr->consumer_->OnConnect();
   });
   return std::unique_ptr<ConsumerEndpoint>(std::move(endpoint));
 }
@@ -473,9 +502,9 @@
   return true;
 }
 
-bool TracingServiceImpl::EnableTracing(ConsumerEndpointImpl* consumer,
-                                       const TraceConfig& cfg,
-                                       base::ScopedFile fd) {
+base::Status TracingServiceImpl::EnableTracing(ConsumerEndpointImpl* consumer,
+                                               const TraceConfig& cfg,
+                                               base::ScopedFile fd) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DLOG("Enabling tracing for consumer %p",
                 reinterpret_cast<void*>(consumer));
@@ -486,19 +515,18 @@
   TracingSession* tracing_session =
       GetTracingSession(consumer->tracing_session_id_);
   if (tracing_session) {
-    PERFETTO_DLOG(
-        "A Consumer is trying to EnableTracing() but another tracing session "
-        "is already active (forgot a call to FreeBuffers() ?)");
-    return false;
+    return PERFETTO_SVC_ERR(
+        "A Consumer is trying to EnableTracing() but another tracing "
+        "session is already active (forgot a call to FreeBuffers() ?)");
   }
 
   const uint32_t max_duration_ms = cfg.enable_extra_guardrails()
                                        ? kGuardrailsMaxTracingDurationMillis
                                        : kMaxTracingDurationMillis;
   if (cfg.duration_ms() > max_duration_ms) {
-    PERFETTO_ELOG("Requested too long trace (%" PRIu32 "ms  > %" PRIu32 " ms)",
-                  cfg.duration_ms(), max_duration_ms);
-    return false;
+    return PERFETTO_SVC_ERR("Requested too long trace (%" PRIu32
+                            "ms  > %" PRIu32 " ms)",
+                            cfg.duration_ms(), max_duration_ms);
   }
 
   const bool has_trigger_config = cfg.trigger_config().trigger_mode() !=
@@ -506,17 +534,15 @@
   if (has_trigger_config && (cfg.trigger_config().trigger_timeout_ms() == 0 ||
                              cfg.trigger_config().trigger_timeout_ms() >
                                  kGuardrailsMaxTracingDurationMillis)) {
-    PERFETTO_ELOG(
+    return PERFETTO_SVC_ERR(
         "Traces with START_TRACING triggers must provide a positive "
         "trigger_timeout_ms < 7 days (received %" PRIu32 "ms)",
         cfg.trigger_config().trigger_timeout_ms());
-    return false;
   }
 
   if (has_trigger_config && cfg.duration_ms() != 0) {
-    PERFETTO_ELOG(
+    return PERFETTO_SVC_ERR(
         "duration_ms was set, this must not be set for traces with triggers.");
-    return false;
   }
 
   if (cfg.trigger_config().trigger_mode() ==
@@ -527,50 +553,52 @@
     // drain the events in ReadBuffers because we are waiting for STOP_TRACING,
     // we can end up queueing up a lot of TracingServiceEvents and emitting them
     // wildy out of order breaking windowed sorting in trace processor).
-    PERFETTO_ELOG(
+    return PERFETTO_SVC_ERR(
         "Specifying trigger mode STOP_TRACING and write_into_file together is "
         "unsupported");
-    return false;
   }
 
   std::unordered_set<std::string> triggers;
   for (const auto& trigger : cfg.trigger_config().triggers()) {
     if (!triggers.insert(trigger.name()).second) {
-      PERFETTO_ELOG("Duplicate trigger name: %s", trigger.name().c_str());
-      return false;
+      return PERFETTO_SVC_ERR("Duplicate trigger name: %s",
+                              trigger.name().c_str());
     }
   }
 
   if (cfg.enable_extra_guardrails()) {
     if (cfg.deferred_start()) {
-      PERFETTO_ELOG(
+      return PERFETTO_SVC_ERR(
           "deferred_start=true is not supported in unsupervised traces");
-      return false;
     }
     uint64_t buf_size_sum = 0;
-    for (const auto& buf : cfg.buffers())
+    for (const auto& buf : cfg.buffers()) {
+      if (buf.size_kb() % 4 != 0) {
+        return PERFETTO_SVC_ERR(
+            "buffers.size_kb must be a multiple of 4, got %" PRIu32,
+            buf.size_kb());
+      }
       buf_size_sum += buf.size_kb();
+    }
     if (buf_size_sum > kGuardrailsMaxTracingBufferSizeKb) {
-      PERFETTO_ELOG("Requested too large trace buffer (%" PRIu64
-                    "kB  > %" PRIu32 " kB)",
-                    buf_size_sum, kGuardrailsMaxTracingBufferSizeKb);
-      return false;
+      return PERFETTO_SVC_ERR("Requested too large trace buffer (%" PRIu64
+                              "kB  > %" PRIu32 " kB)",
+                              buf_size_sum, kGuardrailsMaxTracingBufferSizeKb);
     }
   }
 
   if (cfg.buffers_size() > kMaxBuffersPerConsumer) {
-    PERFETTO_ELOG("Too many buffers configured (%d)", cfg.buffers_size());
-    return false;
+    return PERFETTO_SVC_ERR("Too many buffers configured (%d)",
+                            cfg.buffers_size());
   }
 
   if (!cfg.unique_session_name().empty()) {
     const std::string& name = cfg.unique_session_name();
     for (auto& kv : tracing_sessions_) {
       if (kv.second.config.unique_session_name() == name) {
-        PERFETTO_ELOG(
+        return PERFETTO_SVC_ERR(
             "A trace with this unique session name (%s) already exists",
             name.c_str());
-        return false;
       }
     }
   }
@@ -595,29 +623,27 @@
     if (previous_s == 0) {
       previous_s = now_s;
     } else {
-      PERFETTO_ELOG(
+      return PERFETTO_SVC_ERR(
           "A trace with unique session name \"%s\" began less than %" PRId64
           "s ago (%" PRId64 "s)",
           name.c_str(), kMinSecondsBetweenTracesGuardrail, now_s - previous_s);
-      return false;
     }
   }
 
-  const long sessions_for_uid = std::count_if(
+  const int sessions_for_uid = static_cast<int>(std::count_if(
       tracing_sessions_.begin(), tracing_sessions_.end(),
       [consumer](const decltype(tracing_sessions_)::value_type& s) {
         return s.second.consumer_uid == consumer->uid_;
-      });
+      }));
 
   int per_uid_limit = kMaxConcurrentTracingSessionsPerUid;
   if (consumer->uid_ == 1066 /* AID_STATSD*/) {
     per_uid_limit = kMaxConcurrentTracingSessionsForStatsdUid;
   }
   if (sessions_for_uid >= per_uid_limit) {
-    PERFETTO_ELOG(
-        "Too many concurrent tracing sesions (%ld) for uid %d limit is %d",
+    return PERFETTO_SVC_ERR(
+        "Too many concurrent tracing sesions (%d) for uid %d limit is %d",
         sessions_for_uid, static_cast<int>(consumer->uid_), per_uid_limit);
-    return false;
   }
 
   // TODO(primiano): This is a workaround to prevent that a producer gets stuck
@@ -625,9 +651,8 @@
   // instances than free pages in the buffer. This is really a bug in
   // trace_probes and the way it handles stalls in the shmem buffer.
   if (tracing_sessions_.size() >= kMaxConcurrentTracingSessions) {
-    PERFETTO_ELOG("Too many concurrent tracing sesions (%zu)",
-                  tracing_sessions_.size());
-    return false;
+    return PERFETTO_SVC_ERR("Too many concurrent tracing sesions (%zu)",
+                            tracing_sessions_.size());
   }
 
   const TracingSessionID tsid = ++last_tracing_session_id_;
@@ -637,17 +662,17 @@
 
   if (cfg.write_into_file()) {
     if (!fd ^ !cfg.output_path().empty()) {
-      PERFETTO_ELOG(
+      tracing_sessions_.erase(tsid);
+      return PERFETTO_SVC_ERR(
           "When write_into_file==true either a FD needs to be passed or "
           "output_path must be populated (but not both)");
-      tracing_sessions_.erase(tsid);
-      return false;
     }
     if (!cfg.output_path().empty()) {
-      fd = CreateTraceFile(cfg.output_path());
+      fd = CreateTraceFile(cfg.output_path(), /*overwrite=*/false);
       if (!fd) {
         tracing_sessions_.erase(tsid);
-        return false;
+        return PERFETTO_SVC_ERR("Failed to create the trace file %s",
+                                cfg.output_path().c_str());
       }
     }
     tracing_session->write_into_file = std::move(fd);
@@ -708,7 +733,8 @@
       buffers_.erase(global_id);
     }
     tracing_sessions_.erase(tsid);
-    return false;
+    return PERFETTO_SVC_ERR(
+        "Failed to allocate tracing buffers: OOM or too many buffers");
   }
 
   consumer->tracing_session_id_ = tsid;
@@ -778,7 +804,7 @@
   if (!cfg.deferred_start() && !has_start_trigger)
     return StartTracing(tsid);
 
-  return true;
+  return base::OkStatus();
 }
 
 void TracingServiceImpl::ChangeTraceConfig(ConsumerEndpointImpl* consumer,
@@ -824,7 +850,7 @@
     std::vector<std::string> new_producer_name_filter;
     std::vector<std::string> new_producer_name_regex_filter;
     bool found_data_source = false;
-    for (auto it : updated_cfg.data_sources()) {
+    for (const auto& it : updated_cfg.data_sources()) {
       if (cfg_data_source.config().name() == it.config().name()) {
         new_producer_name_filter = it.producer_name_filter();
         new_producer_name_regex_filter = it.producer_name_regex_filter();
@@ -899,18 +925,17 @@
   }
 }
 
-bool TracingServiceImpl::StartTracing(TracingSessionID tsid) {
+base::Status TracingServiceImpl::StartTracing(TracingSessionID tsid) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   TracingSession* tracing_session = GetTracingSession(tsid);
   if (!tracing_session) {
-    PERFETTO_DLOG("StartTracing() failed, invalid session ID %" PRIu64, tsid);
-    return false;
+    return PERFETTO_SVC_ERR(
+        "StartTracing() failed, invalid session ID %" PRIu64, tsid);
   }
 
   if (tracing_session->state != TracingSession::CONFIGURED) {
-    PERFETTO_DLOG("StartTracing() failed, invalid session state: %d",
-                  tracing_session->state);
-    return false;
+    return PERFETTO_SVC_ERR("StartTracing() failed, invalid session state: %d",
+                            tracing_session->state);
   }
 
   tracing_session->state = TracingSession::STARTED;
@@ -1001,7 +1026,9 @@
     }
     StartDataSourceInstance(producer, tracing_session, &data_source);
   }
-  return true;
+
+  MaybeNotifyAllDataSourcesStarted(tracing_session);
+  return base::OkStatus();
 }
 
 void TracingServiceImpl::StartDataSourceInstance(
@@ -1341,8 +1368,13 @@
     ReadBuffers(tracing_session->id, nullptr);
   }
 
+  if (tracing_session->on_disable_callback_for_bugreport) {
+    std::move(tracing_session->on_disable_callback_for_bugreport)();
+    tracing_session->on_disable_callback_for_bugreport = nullptr;
+  }
+
   if (tracing_session->consumer_maybe_null)
-    tracing_session->consumer_maybe_null->NotifyOnTracingDisabled();
+    tracing_session->consumer_maybe_null->NotifyOnTracingDisabled("");
 }
 
 void TracingServiceImpl::Flush(TracingSessionID tsid,
@@ -1453,14 +1485,15 @@
                                        ConsumerEndpoint::FlushCallback callback,
                                        bool success) {
   TracingSession* tracing_session = GetTracingSession(tsid);
-  if (tracing_session) {
-    // Producers may not have been able to flush all their data, even if they
-    // indicated flush completion. If possible, also collect uncommitted chunks
-    // to make sure we have everything they wrote so far.
-    for (auto& producer_id_and_producer : producers_) {
-      ScrapeSharedMemoryBuffers(tracing_session,
-                                producer_id_and_producer.second);
-    }
+  if (!tracing_session) {
+    callback(false);
+    return;
+  }
+  // Producers may not have been able to flush all their data, even if they
+  // indicated flush completion. If possible, also collect uncommitted chunks
+  // to make sure we have everything they wrote so far.
+  for (auto& producer_id_and_producer : producers_) {
+    ScrapeSharedMemoryBuffers(tracing_session, producer_id_and_producer.second);
   }
   SnapshotLifecyleEvent(
       tracing_session,
@@ -1624,7 +1657,8 @@
         if (weak_this)
           weak_this->PeriodicFlushTask(tsid, /*post_next_only=*/false);
       },
-      flush_period_ms - (base::GetWallTimeMs().count() % flush_period_ms));
+      flush_period_ms - static_cast<uint32_t>(base::GetWallTimeMs().count() %
+                                              flush_period_ms));
 
   if (post_next_only)
     return;
@@ -1653,7 +1687,8 @@
           weak_this->PeriodicClearIncrementalStateTask(
               tsid, /*post_next_only=*/false);
       },
-      clear_period_ms - (base::GetWallTimeMs().count() % clear_period_ms));
+      clear_period_ms - static_cast<uint32_t>(base::GetWallTimeMs().count() %
+                                              clear_period_ms));
 
   if (post_next_only)
     return;
@@ -1728,6 +1763,18 @@
   std::vector<TracePacket> packets;
   packets.reserve(1024);  // Just an educated guess to avoid trivial expansions.
 
+  // If a bugreport request happened and the trace was stolen for that, give
+  // an empty trace with a clear signal to the consumer.
+  if (tracing_session->seized_for_bugreport && consumer) {
+    if (!tracing_session->config.builtin_data_sources()
+             .disable_service_events()) {
+      EmitSeizedForBugreportLifecycleEvent(&packets);
+    }
+    EmitLifecycleEvents(tracing_session, &packets);
+    consumer->consumer_->OnTraceData(std::move(packets), /*has_more=*/false);
+    return true;
+  }
+
   if (!tracing_session->initial_clock_snapshot.empty()) {
     EmitClockSnapshot(tracing_session,
                       std::move(tracing_session->initial_clock_snapshot),
@@ -1958,7 +2005,7 @@
   }  // if (tracing_session->write_into_file)
 
   if (has_more) {
-    auto weak_consumer = consumer->GetWeakPtr();
+    auto weak_consumer = consumer->weak_ptr_factory_.GetWeakPtr();
     auto weak_this = weak_ptr_factory_.GetWeakPtr();
     task_runner_->PostTask([weak_this, weak_consumer, tsid] {
       if (!weak_this || !weak_consumer)
@@ -2090,11 +2137,16 @@
       if (it->first == producer_id && it->second.data_source_name == name) {
         DataSourceInstanceID ds_inst_id = it->second.instance_id;
         if (it->second.state != DataSourceInstance::STOPPED) {
-          if (it->second.state != DataSourceInstance::STOPPING)
+          if (it->second.state != DataSourceInstance::STOPPING) {
             StopDataSourceInstance(producer, &kv.second, &it->second,
                                    /* disable_immediately = */ false);
+          }
+
           // Mark the instance as stopped immediately, since we are
           // unregistering it below.
+          //
+          //  The StopDataSourceInstance above might have set the state to
+          //  STOPPING so this condition isn't an else.
           if (it->second.state == DataSourceInstance::STOPPING)
             NotifyDataSourceStopped(producer_id, ds_inst_id);
         }
@@ -2472,7 +2524,8 @@
           return;
         weak_this->PeriodicSnapshotTask(tracing_session_ptr);
       },
-      interval_ms - (base::GetWallTimeMs().count() % interval_ms));
+      interval_ms -
+          static_cast<uint32_t>(base::GetWallTimeMs().count() % interval_ms));
 }
 
 void TracingServiceImpl::SnapshotLifecyleEvent(TracingSession* tracing_session,
@@ -2550,8 +2603,8 @@
 
   TracingSession::ClockSnapshotData new_snapshot_data;
 
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
-    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&    \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
+    !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&   \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
   struct {
     clockid_t id;
@@ -2581,9 +2634,9 @@
         static_cast<uint32_t>(clock.type),
         static_cast<uint64_t>(base::FromPosixTimespec(clock.ts).count())));
   }
-#else   // !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) &&
-        // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&
-        // !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
+#else  // !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) &&
+       // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&
+       // !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
   auto wall_time_ns = static_cast<uint64_t>(base::GetWallTimeNs().count());
   // The default trace clock is boot time, so we always need to emit a path to
   // it. However since we don't actually have a boot time source on these
@@ -2592,7 +2645,7 @@
       std::make_pair(protos::pbzero::BUILTIN_CLOCK_BOOTTIME, wall_time_ns));
   new_snapshot_data.push_back(
       std::make_pair(protos::pbzero::BUILTIN_CLOCK_MONOTONIC, wall_time_ns));
-#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) &&
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) &&
         // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) &&
         // !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
 
@@ -2799,6 +2852,18 @@
     SerializeAndAppendPacket(packets, std::move(pair.second));
 }
 
+void TracingServiceImpl::EmitSeizedForBugreportLifecycleEvent(
+    std::vector<TracePacket>* packets) {
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
+  packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
+  packet->set_trusted_uid(static_cast<int32_t>(uid_));
+  packet->set_trusted_packet_sequence_id(kServicePacketSequenceID);
+  auto* service_event = packet->set_service_event();
+  service_event->AppendVarInt(
+      protos::pbzero::TracingServiceEvent::kSeizedForBugreportFieldNumber, 1);
+  SerializeAndAppendPacket(packets, packet.SerializeAsArray());
+}
+
 void TracingServiceImpl::MaybeEmitReceivedTriggers(
     TracingSession* tracing_session,
     std::vector<TracePacket>* packets) {
@@ -2821,6 +2886,57 @@
   }
 }
 
+bool TracingServiceImpl::MaybeSaveTraceForBugreport(
+    std::function<void()> callback) {
+  TracingSession* max_session = nullptr;
+  TracingSessionID max_tsid = 0;
+  for (auto& session_id_and_session : tracing_sessions_) {
+    auto& session = session_id_and_session.second;
+    const int32_t score = session.config.bugreport_score();
+    // Exclude sessions with 0 (or below) score. By default tracing sessions
+    // should NOT be eligible to be attached to bugreports. Also don't try to
+    // steal long traces with write_into_file as their content is already
+    // partially flushed onto a file and we can't easily recover it (also that
+    // could lead to enormous traces).
+    if (score <= 0 || session.state != TracingSession::STARTED ||
+        session.write_into_file) {
+      continue;
+    }
+
+    // If we are already in the process of finalizing another trace for
+    // bugreport, don't even start another one, as they would try to write onto
+    // the same file.
+    if (session.on_disable_callback_for_bugreport)
+      return false;
+
+    if (!max_session || score > max_session->config.bugreport_score()) {
+      max_session = &session;
+      max_tsid = session_id_and_session.first;
+    }
+  }
+
+  // No eligible trace found.
+  if (!max_session)
+    return false;
+
+  auto br_fd = CreateTraceFile(GetBugreportTmpPath(), /*overwrite=*/true);
+  if (!br_fd)
+    return false;
+  max_session->write_into_file = std::move(br_fd);
+  max_session->on_disable_callback_for_bugreport = std::move(callback);
+  max_session->seized_for_bugreport = true;
+
+  // Post a task to avoid that early FlushAndDisableTracing() failures invoke
+  // the callback before we return. That would re-enter in a weird way the
+  // callstack of the calling ConsumerEndpointImpl::SaveTraceForBugreport().
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  task_runner_->PostTask([weak_this, max_tsid] {
+    if (weak_this)
+      weak_this->FlushAndDisableTracing(max_tsid);
+  });
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // TracingServiceImpl::ConsumerEndpointImpl implementation
 ////////////////////////////////////////////////////////////////////////////////
@@ -2841,12 +2957,13 @@
   consumer_->OnDisconnect();
 }
 
-void TracingServiceImpl::ConsumerEndpointImpl::NotifyOnTracingDisabled() {
+void TracingServiceImpl::ConsumerEndpointImpl::NotifyOnTracingDisabled(
+    const std::string& error) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  auto weak_this = GetWeakPtr();
-  task_runner_->PostTask([weak_this] {
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  task_runner_->PostTask([weak_this, error /* deliberate copy */] {
     if (weak_this)
-      weak_this->consumer_->OnTracingDisabled();
+      weak_this->consumer_->OnTracingDisabled(error);
   });
 }
 
@@ -2854,8 +2971,9 @@
     const TraceConfig& cfg,
     base::ScopedFile fd) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  if (!service_->EnableTracing(this, cfg, std::move(fd)))
-    NotifyOnTracingDisabled();
+  auto status = service_->EnableTracing(this, cfg, std::move(fd));
+  if (!status.ok())
+    NotifyOnTracingDisabled(status.message());
 }
 
 void TracingServiceImpl::ConsumerEndpointImpl::ChangeTraceConfig(
@@ -2922,7 +3040,7 @@
 void TracingServiceImpl::ConsumerEndpointImpl::Detach(const std::string& key) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   bool success = service_->DetachConsumer(this, key);
-  auto weak_this = GetWeakPtr();
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
   task_runner_->PostTask([weak_this, success] {
     if (weak_this)
       weak_this->consumer_->OnDetach(success);
@@ -2932,7 +3050,7 @@
 void TracingServiceImpl::ConsumerEndpointImpl::Attach(const std::string& key) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   bool success = service_->AttachConsumer(this, key);
-  auto weak_this = GetWeakPtr();
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
   task_runner_->PostTask([weak_this, success] {
     if (!weak_this)
       return;
@@ -2956,7 +3074,7 @@
     success = true;
     stats = service_->GetTraceStats(session);
   }
-  auto weak_this = GetWeakPtr();
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
   task_runner_->PostTask([weak_this, success, stats] {
     if (weak_this)
       weak_this->consumer_->OnTraceStats(success, stats);
@@ -3022,18 +3140,12 @@
   observable_events->set_all_data_sources_started(true);
 }
 
-base::WeakPtr<TracingServiceImpl::ConsumerEndpointImpl>
-TracingServiceImpl::ConsumerEndpointImpl::GetWeakPtr() {
-  PERFETTO_DCHECK_THREAD(thread_checker_);
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
 ObservableEvents*
 TracingServiceImpl::ConsumerEndpointImpl::AddObservableEvents() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   if (!observable_events_) {
     observable_events_.reset(new ObservableEvents());
-    auto weak_this = GetWeakPtr();
+    auto weak_this = weak_ptr_factory_.GetWeakPtr();
     task_runner_->PostTask([weak_this] {
       if (!weak_this)
         return;
@@ -3090,6 +3202,25 @@
   callback(caps);
 }
 
+void TracingServiceImpl::ConsumerEndpointImpl::SaveTraceForBugreport(
+    SaveTraceForBugreportCallback consumer_callback) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  auto on_complete_callback = [consumer_callback] {
+    if (rename(GetBugreportTmpPath().c_str(), kBugreportTracePath)) {
+      consumer_callback(false, "rename(" + GetBugreportTmpPath() + ", " +
+                                   kBugreportTracePath + ") failed (" +
+                                   strerror(errno) + ")");
+    } else {
+      consumer_callback(true, kBugreportTracePath);
+    }
+  };
+  if (!service_->MaybeSaveTraceForBugreport(std::move(on_complete_callback))) {
+    consumer_callback(false,
+                      "No trace with TraceConfig.bugreport_score > 0 eligible "
+                      "for bug reporting was found");
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // TracingServiceImpl::ProducerEndpointImpl implementation
 ////////////////////////////////////////////////////////////////////////////////
@@ -3230,6 +3361,7 @@
     inproc_shmem_arbiter_.reset(new SharedMemoryArbiterImpl(
         shared_memory_->start(), shared_memory_->size(),
         shared_buffer_page_size_kb_ * 1024, this, task_runner_));
+    inproc_shmem_arbiter_->SetDirectSMBPatchingSupportedByService();
   }
 
   OnTracingSetup();
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 221adf9..c43b1c5 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -27,6 +27,7 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/circular_queue.h"
 #include "perfetto/ext/base/optional.h"
@@ -62,7 +63,7 @@
   struct DataSourceInstance;
 
  public:
-  static constexpr size_t kDefaultShmPageSize = base::kPageSize;
+  static constexpr size_t kDefaultShmPageSize = 4096ul;
   static constexpr size_t kDefaultShmSize = 256 * 1024ul;
   static constexpr size_t kMaxShmSize = 32 * 1024 * 1024ul;
   static constexpr uint32_t kDataSourceStopTimeoutMs = 5000;
@@ -180,8 +181,7 @@
                          uid_t uid);
     ~ConsumerEndpointImpl() override;
 
-    void NotifyOnTracingDisabled();
-    base::WeakPtr<ConsumerEndpointImpl> GetWeakPtr();
+    void NotifyOnTracingDisabled(const std::string& error);
 
     // TracingService::ConsumerEndpoint implementation.
     void EnableTracing(const TraceConfig&, base::ScopedFile) override;
@@ -197,6 +197,7 @@
     void ObserveEvents(uint32_t enabled_event_types) override;
     void QueryServiceState(QueryServiceStateCallback) override;
     void QueryCapabilities(QueryCapabilitiesCallback) override;
+    void SaveTraceForBugreport(SaveTraceForBugreportCallback) override;
 
     // Will queue a task to notify the consumer about the state change.
     void OnDataSourceInstanceStateChange(const ProducerEndpointImpl&,
@@ -259,12 +260,12 @@
   bool DetachConsumer(ConsumerEndpointImpl*, const std::string& key);
   bool AttachConsumer(ConsumerEndpointImpl*, const std::string& key);
   void DisconnectConsumer(ConsumerEndpointImpl*);
-  bool EnableTracing(ConsumerEndpointImpl*,
-                     const TraceConfig&,
-                     base::ScopedFile);
+  base::Status EnableTracing(ConsumerEndpointImpl*,
+                             const TraceConfig&,
+                             base::ScopedFile);
   void ChangeTraceConfig(ConsumerEndpointImpl*, const TraceConfig&);
 
-  bool StartTracing(TracingSessionID);
+  base::Status StartTracing(TracingSessionID);
   void DisableTracing(TracingSessionID, bool disable_immediately = false);
   void Flush(TracingSessionID tsid,
              uint32_t timeout_ms,
@@ -366,7 +367,8 @@
     uint32_t delay_to_next_write_period_ms() const {
       PERFETTO_DCHECK(write_period_ms > 0);
       return write_period_ms -
-             (base::GetWallTimeMs().count() % write_period_ms);
+             static_cast<uint32_t>(base::GetWallTimeMs().count() %
+                                   write_period_ms);
     }
 
     uint32_t flush_timeout_ms() {
@@ -544,6 +546,11 @@
     uint32_t write_period_ms = 0;
     uint64_t max_file_size_bytes = 0;
     uint64_t bytes_written_into_file = 0;
+
+    // Set when using SaveTraceForBugreport(). This callback will be called
+    // when the tracing session ends and the data has been saved into the file.
+    std::function<void()> on_disable_callback_for_bugreport;
+    bool seized_for_bugreport = false;
   };
 
   TracingServiceImpl(const TracingServiceImpl&) = delete;
@@ -569,30 +576,32 @@
   // shared memory and trace buffers.
   void UpdateMemoryGuardrail();
 
-  void StartDataSourceInstance(ProducerEndpointImpl* producer,
-                               TracingSession* tracing_session,
-                               DataSourceInstance* instance);
-  void StopDataSourceInstance(ProducerEndpointImpl* producer,
-                              TracingSession* tracing_session,
-                              DataSourceInstance* instance,
+  void StartDataSourceInstance(ProducerEndpointImpl*,
+                               TracingSession*,
+                               DataSourceInstance*);
+  void StopDataSourceInstance(ProducerEndpointImpl*,
+                              TracingSession*,
+                              DataSourceInstance*,
                               bool disable_immediately);
-  void PeriodicSnapshotTask(TracingSession* tracing_session);
+  void PeriodicSnapshotTask(TracingSession*);
   void MaybeSnapshotClocksIntoRingBuffer(TracingSession*);
   bool SnapshotClocks(TracingSession::ClockSnapshotData*);
   void SnapshotLifecyleEvent(TracingSession*,
                              uint32_t field_id,
                              bool snapshot_clocks);
-  void EmitClockSnapshot(TracingSession* tracing_session,
+  void EmitClockSnapshot(TracingSession*,
                          TracingSession::ClockSnapshotData,
                          std::vector<TracePacket>*);
   void EmitSyncMarker(std::vector<TracePacket>*);
   void EmitStats(TracingSession*, std::vector<TracePacket>*);
-  TraceStats GetTraceStats(TracingSession* tracing_session);
-  void EmitLifecycleEvents(TracingSession*, std::vector<TracePacket>* packets);
+  TraceStats GetTraceStats(TracingSession*);
+  void EmitLifecycleEvents(TracingSession*, std::vector<TracePacket>*);
+  void EmitSeizedForBugreportLifecycleEvent(std::vector<TracePacket>*);
   void MaybeEmitTraceConfig(TracingSession*, std::vector<TracePacket>*);
   void MaybeEmitSystemInfo(TracingSession*, std::vector<TracePacket>*);
   void MaybeEmitReceivedTriggers(TracingSession*, std::vector<TracePacket>*);
   void MaybeNotifyAllDataSourcesStarted(TracingSession*);
+  bool MaybeSaveTraceForBugreport(std::function<void()> callback);
   void OnFlushTimeout(TracingSessionID, FlushRequestID);
   void OnDisableTracingTimeout(TracingSessionID);
   void DisableTracingNotifyConsumerAndFlushFile(TracingSession*);
@@ -600,8 +609,7 @@
   void CompleteFlush(TracingSessionID tsid,
                      ConsumerEndpoint::FlushCallback callback,
                      bool success);
-  void ScrapeSharedMemoryBuffers(TracingSession* tracing_session,
-                                 ProducerEndpointImpl* producer);
+  void ScrapeSharedMemoryBuffers(TracingSession*, ProducerEndpointImpl*);
   void PeriodicClearIncrementalStateTask(TracingSessionID, bool post_next_only);
   TraceBuffer* GetBufferByID(BufferID);
   void OnStartTriggersTimeout(TracingSessionID tsid);
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index d355023..2830045 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -62,6 +62,7 @@
 using ::testing::Property;
 using ::testing::StrictMock;
 using ::testing::StringMatchResultListener;
+using ::testing::StrNe;
 
 namespace perfetto {
 
@@ -706,16 +707,18 @@
   auto checkpoint_name = "on_tracing_disabled_consumer_1_and_2";
   auto on_tracing_disabled = task_runner.CreateCheckpoint(checkpoint_name);
   std::atomic<size_t> counter(0);
-  EXPECT_CALL(*consumer_1, OnTracingDisabled()).WillOnce(Invoke([&]() {
-    if (++counter == 2u) {
-      on_tracing_disabled();
-    }
-  }));
-  EXPECT_CALL(*consumer_2, OnTracingDisabled()).WillOnce(Invoke([&]() {
-    if (++counter == 2u) {
-      on_tracing_disabled();
-    }
-  }));
+  EXPECT_CALL(*consumer_1, OnTracingDisabled(_))
+      .WillOnce(InvokeWithoutArgs([&]() {
+        if (++counter == 2u) {
+          on_tracing_disabled();
+        }
+      }));
+  EXPECT_CALL(*consumer_2, OnTracingDisabled(_))
+      .WillOnce(InvokeWithoutArgs([&]() {
+        if (++counter == 2u) {
+          on_tracing_disabled();
+        }
+      }));
 
   EXPECT_CALL(*producer, StopDataSource(id1));
   EXPECT_CALL(*producer, StopDataSource(id2));
@@ -2775,9 +2778,9 @@
   EXPECT_CALL(*producer, SetupDataSource(_, _)).Times(0);
   consumer->EnableTracing(trace_config);
 
-  // The trace is aborted immediately, 5s here is just some slack for the thread
-  // ping-pongs for slow devices.
-  consumer->WaitForTracingDisabled(5000);
+  // The trace is aborted immediately, the default timeout here is just some
+  // slack for the thread ping-pongs for slow devices.
+  consumer->WaitForTracingDisabled();
 }
 
 TEST_F(TracingServiceImplTest, GetTraceStats) {
@@ -3035,6 +3038,40 @@
   }
 }
 
+TEST_F(TracingServiceImplTest,
+       ObserveAllDataSourceStartedWithoutMatchingInstances) {
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(128);
+
+  consumer->ObserveEvents(ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED);
+
+  // EnableTracing() should immediately cause ALL_DATA_SOURCES_STARTED, because
+  // there aren't any matching data sources registered.
+  consumer->EnableTracing(trace_config);
+
+  auto events = consumer->WaitForObservableEvents();
+  ObservableEvents::DataSourceInstanceStateChange change;
+  EXPECT_TRUE(events.all_data_sources_started());
+
+  consumer->DisableTracing();
+  consumer->WaitForTracingDisabled();
+
+  EXPECT_THAT(
+      consumer->ReadBuffers(),
+      Contains(Property(
+          &protos::gen::TracePacket::service_event,
+          Property(&protos::gen::TracingServiceEvent::all_data_sources_started,
+                   Eq(true)))));
+  consumer->FreeBuffers();
+
+  task_runner.RunUntilIdle();
+
+  Mock::VerifyAndClearExpectations(consumer.get());
+}
+
 // Similar to ObserveAllDataSourceStarted, but covers the case of some data
 // sources not supporting the |notify_on_start|.
 TEST_F(TracingServiceImplTest, ObserveAllDataSourceStartedOnlySomeWillAck) {
@@ -3326,14 +3363,15 @@
 
   // Create a bunch of legit sessions (2 uids * 5 sessions).
   for (int i = 0; i < kMaxConcurrentTracingSessionsPerUid * kUids; i++) {
-    start_new_session(/*uid=*/i % kUids);
+    start_new_session(/*uid=*/static_cast<uid_t>(i) % kUids);
   }
 
   // Any other session now should fail for the two uids.
   for (int i = 0; i <= kUids; i++) {
-    auto* consumer = start_new_session(/*uid=*/i % kUids);
+    auto* consumer = start_new_session(/*uid=*/static_cast<uid_t>(i) % kUids);
     auto on_fail = task_runner.CreateCheckpoint("uid_" + std::to_string(i));
-    EXPECT_CALL(*consumer, OnTracingDisabled()).WillOnce(Invoke(on_fail));
+    EXPECT_CALL(*consumer, OnTracingDisabled(StrNe("")))
+        .WillOnce(InvokeWithoutArgs(on_fail));
   }
 
   // Wait for failure (only after both attempts).
diff --git a/src/tracing/interceptor.cc b/src/tracing/interceptor.cc
new file mode 100644
index 0000000..21bd41f
--- /dev/null
+++ b/src/tracing/interceptor.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/interceptor.h"
+
+#include "perfetto/tracing/internal/tracing_muxer.h"
+
+namespace perfetto {
+
+InterceptorBase::~InterceptorBase() = default;
+InterceptorBase::ThreadLocalState::~ThreadLocalState() = default;
+
+// static
+void InterceptorBase::RegisterImpl(
+    const InterceptorDescriptor& descriptor,
+    std::function<std::unique_ptr<InterceptorBase>()> factory,
+    InterceptorBase::TLSFactory tls_factory,
+    InterceptorBase::TracePacketCallback on_trace_packet) {
+  auto* tracing_impl = internal::TracingMuxer::Get();
+  PERFETTO_DCHECK(tracing_impl);
+  if (!tracing_impl) {
+    PERFETTO_ELOG(
+        "Call Tracing::Initialize() before registering interceptors.");
+    return;
+  }
+  tracing_impl->RegisterInterceptor(descriptor, factory, tls_factory,
+                                    on_trace_packet);
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/internal/interceptor_trace_writer.cc b/src/tracing/internal/interceptor_trace_writer.cc
new file mode 100644
index 0000000..fed2c9a
--- /dev/null
+++ b/src/tracing/internal/interceptor_trace_writer.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/internal/interceptor_trace_writer.h"
+
+#include "perfetto/ext/tracing/core/trace_writer.h"
+
+namespace perfetto {
+namespace internal {
+
+// static
+std::atomic<uint32_t> InterceptorTraceWriter::next_sequence_id_{};
+
+InterceptorTraceWriter::InterceptorTraceWriter(
+    std::unique_ptr<InterceptorBase::ThreadLocalState> tls,
+    InterceptorBase::TracePacketCallback packet_callback,
+    DataSourceStaticState* static_state,
+    uint32_t instance_index)
+    : tls_(std::move(tls)),
+      packet_callback_(std::move(packet_callback)),
+      static_state_(static_state),
+      instance_index_(instance_index),
+      sequence_id_(++next_sequence_id_) {}
+
+InterceptorTraceWriter::~InterceptorTraceWriter() = default;
+
+protozero::MessageHandle<protos::pbzero::TracePacket>
+InterceptorTraceWriter::NewTracePacket() {
+  Flush();
+  auto packet = TraceWriter::TracePacketHandle(cur_packet_.get());
+  packet->set_trusted_packet_sequence_id(sequence_id_);
+  return packet;
+}
+
+void InterceptorTraceWriter::Flush(std::function<void()> callback) {
+  if (!cur_packet_.empty()) {
+    InterceptorBase::TracePacketCallbackArgs args{};
+    args.static_state = static_state_;
+    args.instance_index = instance_index_;
+    args.tls = tls_.get();
+
+    const auto& slices = cur_packet_.GetSlices();
+    if (slices.size() == 1) {
+      // Fast path: the current packet fits into a single slice.
+      auto slice_range = slices.begin()->GetUsedRange();
+      args.packet_data = protozero::ConstBytes{
+          slice_range.begin,
+          static_cast<size_t>(slice_range.end - slice_range.begin)};
+      bytes_written_ += static_cast<uint64_t>(args.packet_data.size);
+      packet_callback_(std::move(args));
+    } else {
+      // Fallback: stitch together multiple slices.
+      auto stitched_data = cur_packet_.SerializeAsArray();
+      args.packet_data =
+          protozero::ConstBytes{stitched_data.data(), stitched_data.size()};
+      bytes_written_ += static_cast<uint64_t>(stitched_data.size());
+      packet_callback_(std::move(args));
+    }
+    cur_packet_.Reset();
+  }
+  if (callback)
+    callback();
+}
+
+uint64_t InterceptorTraceWriter::written() const {
+  return bytes_written_;
+}
+
+}  // namespace internal
+}  // namespace perfetto
diff --git a/src/tracing/internal/system_tracing_backend.cc b/src/tracing/internal/system_tracing_backend.cc
index 987f956..5aa3493 100644
--- a/src/tracing/internal/system_tracing_backend.cc
+++ b/src/tracing/internal/system_tracing_backend.cc
@@ -19,6 +19,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
 #include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/ext/tracing/ipc/consumer_ipc_client.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 #include "perfetto/ext/tracing/ipc/producer_ipc_client.h"
 
@@ -40,17 +41,18 @@
   auto endpoint = ProducerIPCClient::Connect(
       GetProducerSocket(), args.producer, args.producer_name, args.task_runner,
       TracingService::ProducerSMBScrapingMode::kEnabled,
-      args.shmem_size_hint_bytes, args.shmem_page_size_hint_bytes);
+      args.shmem_size_hint_bytes, args.shmem_page_size_hint_bytes, nullptr,
+      nullptr, ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable);
   PERFETTO_CHECK(endpoint);
   return endpoint;
 }
 
 std::unique_ptr<ConsumerEndpoint> SystemTracingBackend::ConnectConsumer(
-    const ConnectConsumerArgs&) {
-  PERFETTO_FATAL(
-      "Trace session creation is not supported yet when using the system "
-      "tracing backend. Use the perfetto cmdline client instead to start "
-      "system-wide tracing sessions");
+    const ConnectConsumerArgs& args) {
+  auto endpoint = ConsumerIPCClient::Connect(GetConsumerSocket(), args.consumer,
+                                             args.task_runner);
+  PERFETTO_CHECK(endpoint);
+  return endpoint;
 }
 
 }  // namespace internal
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 7511018..a627518 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -27,18 +27,27 @@
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/thread_checker.h"
 #include "perfetto/ext/base/waitable_event.h"
+#include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
 #include "perfetto/ext/tracing/core/trace_stats.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/ext/tracing/core/tracing_service.h"
 #include "perfetto/tracing/buffer_exhausted_policy.h"
 #include "perfetto/tracing/core/data_source_config.h"
+#include "perfetto/tracing/core/tracing_service_state.h"
 #include "perfetto/tracing/data_source.h"
 #include "perfetto/tracing/internal/data_source_internal.h"
+#include "perfetto/tracing/internal/interceptor_trace_writer.h"
 #include "perfetto/tracing/trace_writer_base.h"
 #include "perfetto/tracing/tracing.h"
 #include "perfetto/tracing/tracing_backend.h"
 
+#include "protos/perfetto/config/interceptor_config.gen.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <unistd.h>  // For dup()
+#endif
+
 namespace perfetto {
 namespace internal {
 
@@ -65,14 +74,37 @@
 }  // namespace
 
 // ----- Begin of TracingMuxerImpl::ProducerImpl
-TracingMuxerImpl::ProducerImpl::ProducerImpl(TracingMuxerImpl* muxer,
-                                             TracingBackendId backend_id)
-    : muxer_(muxer), backend_id_(backend_id) {}
+TracingMuxerImpl::ProducerImpl::ProducerImpl(
+    TracingMuxerImpl* muxer,
+    TracingBackendId backend_id,
+    uint32_t shmem_batch_commits_duration_ms)
+    : muxer_(muxer),
+      backend_id_(backend_id),
+      shmem_batch_commits_duration_ms_(shmem_batch_commits_duration_ms) {}
+
 TracingMuxerImpl::ProducerImpl::~ProducerImpl() = default;
 
 void TracingMuxerImpl::ProducerImpl::Initialize(
     std::unique_ptr<ProducerEndpoint> endpoint) {
-  service_ = std::move(endpoint);
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  PERFETTO_DCHECK(!connected_);
+  connection_id_++;
+
+  // Adopt the endpoint into a shared pointer so that we can safely share it
+  // across threads that create trace writers. The custom deleter function
+  // ensures that the endpoint is always destroyed on the muxer's thread. (Note
+  // that |task_runner| is assumed to outlive tracing sessions on all threads.)
+  auto* task_runner = muxer_->task_runner_.get();
+  auto deleter = [task_runner](ProducerEndpoint* e) {
+    task_runner->PostTask([e] { delete e; });
+  };
+  std::shared_ptr<ProducerEndpoint> service(endpoint.release(), deleter);
+  // This atomic store is needed because another thread might be concurrently
+  // creating a trace writer using the previous (disconnected) |service_|. See
+  // CreateTraceWriter().
+  std::atomic_store(&service_, std::move(service));
+  // Don't try to use the service here since it may not have connected yet. See
+  // OnConnect().
 }
 
 void TracingMuxerImpl::ProducerImpl::OnConnect() {
@@ -86,19 +118,31 @@
 void TracingMuxerImpl::ProducerImpl::OnDisconnect() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   connected_ = false;
-  // TODO: handle more graceful.
-  PERFETTO_ELOG("Cannot connect to traced. Is it running?");
+  // Active data sources for this producer will be stopped by
+  // DestroyStoppedTraceWritersForCurrentThread() since the reconnected producer
+  // will have a different connection id (even before it has finished
+  // connecting).
+  registered_data_sources_.reset();
+  // Keep the old service around as a dead connection in case it has active
+  // trace writers. We can't clear |service_| here because other threads may be
+  // concurrently creating new trace writers. The reconnection below will
+  // atomically swap the new service in place of the old one.
+  dead_services_.push_back(service_);
+  // Try reconnecting the producer.
+  muxer_->OnProducerDisconnected(this);
 }
 
 void TracingMuxerImpl::ProducerImpl::OnTracingSetup() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
+  service_->MaybeSharedMemoryArbiter()->SetBatchCommitsDuration(
+      shmem_batch_commits_duration_ms_);
 }
 
 void TracingMuxerImpl::ProducerImpl::SetupDataSource(
     DataSourceInstanceID id,
     const DataSourceConfig& cfg) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
-  muxer_->SetupDataSource(backend_id_, id, cfg);
+  muxer_->SetupDataSource(backend_id_, connection_id_, id, cfg);
 }
 
 void TracingMuxerImpl::ProducerImpl::StartDataSource(DataSourceInstanceID id,
@@ -128,13 +172,34 @@
   // TODO(skyostil): Mark each affected data source's incremental state as
   // needing to be cleared.
 }
+
+void TracingMuxerImpl::ProducerImpl::SweepDeadServices() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  auto is_unused = [](const std::shared_ptr<ProducerEndpoint>& endpoint) {
+    auto* arbiter = endpoint->MaybeSharedMemoryArbiter();
+    return !arbiter || arbiter->TryShutdown();
+  };
+  for (auto it = dead_services_.begin(); it != dead_services_.end();) {
+    auto next_it = it;
+    next_it++;
+    if (is_unused(*it)) {
+      dead_services_.erase(it);
+    }
+    it = next_it;
+  }
+}
+
 // ----- End of TracingMuxerImpl::ProducerImpl methods.
 
 // ----- Begin of TracingMuxerImpl::ConsumerImpl
 TracingMuxerImpl::ConsumerImpl::ConsumerImpl(TracingMuxerImpl* muxer,
+                                             BackendType backend_type,
                                              TracingBackendId backend_id,
                                              TracingSessionGlobalID session_id)
-    : muxer_(muxer), backend_id_(backend_id), session_id_(session_id) {}
+    : muxer_(muxer),
+      backend_type_(backend_type),
+      backend_id_(backend_id),
+      session_id_(session_id) {}
 
 TracingMuxerImpl::ConsumerImpl::~ConsumerImpl() = default;
 
@@ -142,8 +207,8 @@
     std::unique_ptr<ConsumerEndpoint> endpoint) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   service_ = std::move(endpoint);
-  // Observe data source instance events so we get notified when tracing starts.
-  service_->ObserveEvents(ObservableEvents::TYPE_DATA_SOURCES_INSTANCES);
+  // Don't try to use the service here since it may not have connected yet. See
+  // OnConnect().
 }
 
 void TracingMuxerImpl::ConsumerImpl::OnConnect() {
@@ -151,29 +216,55 @@
   PERFETTO_DCHECK(!connected_);
   connected_ = true;
 
+  // Observe data source instance events so we get notified when tracing starts.
+  service_->ObserveEvents(ObservableEvents::TYPE_DATA_SOURCES_INSTANCES |
+                          ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED);
+
   // If the API client configured and started tracing before we connected,
   // tell the backend about it now.
-  if (trace_config_) {
+  if (trace_config_)
     muxer_->SetupTracingSession(session_id_, trace_config_);
-    if (start_pending_)
-      muxer_->StartTracingSession(session_id_);
-    if (get_trace_stats_pending_)
-      muxer_->GetTraceStats(session_id_, std::move(get_trace_stats_callback_));
-    if (stop_pending_)
-      muxer_->StopTracingSession(session_id_);
+  if (start_pending_)
+    muxer_->StartTracingSession(session_id_);
+  if (get_trace_stats_pending_) {
+    auto callback = std::move(get_trace_stats_callback_);
+    get_trace_stats_callback_ = nullptr;
+    muxer_->GetTraceStats(session_id_, std::move(callback));
   }
+  if (query_service_state_callback_) {
+    auto callback = std::move(query_service_state_callback_);
+    query_service_state_callback_ = nullptr;
+    muxer_->QueryServiceState(session_id_, std::move(callback));
+  }
+  if (stop_pending_)
+    muxer_->StopTracingSession(session_id_);
 }
 
 void TracingMuxerImpl::ConsumerImpl::OnDisconnect() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  if (!connected_ && backend_type_ == kSystemBackend) {
+    PERFETTO_ELOG(
+        "Unable to connect to the system tracing service as a consumer. On "
+        "Android, use the \"perfetto\" command line tool instead to start "
+        "system-wide tracing sessions");
+  }
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+
+  // Notify the client about disconnection.
+  NotifyError(TracingError{TracingError::kDisconnected, "Peer disconnected"});
+
+  // Make sure the client doesn't hang in a blocking start/stop because of the
+  // disconnection.
+  NotifyStartComplete();
+  NotifyStopComplete();
+
   // It shouldn't be necessary to call StopTracingSession. If we get this call
   // it means that the service did shutdown before us, so there is no point
   // trying it to ask it to stop the session. We should just remember to cleanup
   // the consumer vector.
   connected_ = false;
 
-  // TODO notify the client somehow.
-
   // Notify the muxer that it is safe to destroy |this|. This is needed because
   // the ConsumerEndpoint stored in |service_| requires that |this| be safe to
   // access until OnDisconnect() is called.
@@ -195,10 +286,15 @@
   service_.reset();
 }
 
-void TracingMuxerImpl::ConsumerImpl::OnTracingDisabled() {
+void TracingMuxerImpl::ConsumerImpl::OnTracingDisabled(
+    const std::string& error) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DCHECK(!stopped_);
   stopped_ = true;
+
+  if (!error.empty())
+    NotifyError(TracingError{TracingError::kTracingFailed, error});
+
   // If we're still waiting for the start event, fire it now. This may happen if
   // there are no active data sources in the session.
   NotifyStartComplete();
@@ -218,6 +314,14 @@
   }
 }
 
+void TracingMuxerImpl::ConsumerImpl::NotifyError(const TracingError& error) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  if (error_callback_) {
+    muxer_->task_runner_->PostTask(
+        std::bind(std::move(error_callback_), error));
+  }
+}
+
 void TracingMuxerImpl::ConsumerImpl::NotifyStopComplete() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   if (stop_complete_callback_) {
@@ -280,9 +384,15 @@
           state_change.state() ==
           ObservableEvents::DATA_SOURCE_INSTANCE_STATE_STARTED;
     }
+  }
+
+  if (events.instance_state_changes_size() ||
+      events.all_data_sources_started()) {
     // Data sources are first reported as being stopped before starting, so once
     // all the data sources we know about have started we can declare tracing
-    // begun.
+    // begun. In the case where there are no matching data sources for the
+    // session, the service will report the all_data_sources_started() event
+    // without adding any instances (only since Android S / Perfetto v10.0).
     if (start_complete_callback_ || blocking_start_complete_callback_) {
       bool all_data_sources_started = std::all_of(
           data_source_states_.cbegin(), data_source_states_.cend(),
@@ -337,8 +447,14 @@
   auto session_id = session_id_;
   std::shared_ptr<TraceConfig> trace_config(new TraceConfig(cfg));
   if (fd >= 0) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    PERFETTO_FATAL(
+        "Passing a file descriptor to TracingSession::Setup() is not supported "
+        "on Windows yet. Use TracingSession::ReadTrace() instead");
+#else
     trace_config->set_write_into_file(true);
     fd = dup(fd);
+#endif
   }
   muxer->task_runner_->PostTask([muxer, session_id, trace_config, fd] {
     muxer->SetupTracingSession(session_id, trace_config, base::ScopedFile(fd));
@@ -353,6 +469,16 @@
       [muxer, session_id] { muxer->StartTracingSession(session_id); });
 }
 
+// Can be called from any thread.
+void TracingMuxerImpl::TracingSessionImpl::ChangeTraceConfig(
+    const TraceConfig& cfg) {
+  auto* muxer = muxer_;
+  auto session_id = session_id_;
+  muxer->task_runner_->PostTask([muxer, session_id, cfg] {
+    muxer->ChangeTracingSessionConfig(session_id, cfg);
+  });
+}
+
 // Can be called from any thread except the service thread.
 void TracingMuxerImpl::TracingSessionImpl::StartBlocking() {
   PERFETTO_DCHECK(!muxer_->task_runner_->RunsTasksOnCurrentThread());
@@ -361,6 +487,11 @@
   base::WaitableEvent tracing_started;
   muxer->task_runner_->PostTask([muxer, session_id, &tracing_started] {
     auto* consumer = muxer->FindConsumer(session_id);
+    if (!consumer) {
+      // TODO(skyostil): Signal an error to the user.
+      tracing_started.Notify();
+      return;
+    }
     PERFETTO_DCHECK(!consumer->blocking_start_complete_callback_);
     consumer->blocking_start_complete_callback_ = [&] {
       tracing_started.Notify();
@@ -371,6 +502,23 @@
 }
 
 // Can be called from any thread.
+void TracingMuxerImpl::TracingSessionImpl::Flush(
+    std::function<void(bool)> user_callback,
+    uint32_t timeout_ms) {
+  auto* muxer = muxer_;
+  auto session_id = session_id_;
+  muxer->task_runner_->PostTask([muxer, session_id, timeout_ms, user_callback] {
+    auto* consumer = muxer->FindConsumer(session_id);
+    if (!consumer) {
+      std::move(user_callback)(false);
+      return;
+    }
+    muxer->FlushTracingSession(session_id, timeout_ms,
+                               std::move(user_callback));
+  });
+}
+
+// Can be called from any thread.
 void TracingMuxerImpl::TracingSessionImpl::Stop() {
   auto* muxer = muxer_;
   auto session_id = session_id_;
@@ -386,6 +534,11 @@
   base::WaitableEvent tracing_stopped;
   muxer->task_runner_->PostTask([muxer, session_id, &tracing_stopped] {
     auto* consumer = muxer->FindConsumer(session_id);
+    if (!consumer) {
+      // TODO(skyostil): Signal an error to the user.
+      tracing_stopped.Notify();
+      return;
+    }
     PERFETTO_DCHECK(!consumer->blocking_stop_complete_callback_);
     consumer->blocking_stop_complete_callback_ = [&] {
       tracing_stopped.Notify();
@@ -415,6 +568,19 @@
   });
 }
 
+// Can be called from any thread
+void TracingMuxerImpl::TracingSessionImpl::SetOnErrorCallback(
+    std::function<void(TracingError)> cb) {
+  auto* muxer = muxer_;
+  auto session_id = session_id_;
+  muxer->task_runner_->PostTask([muxer, session_id, cb] {
+    auto* consumer = muxer->FindConsumer(session_id);
+    if (!consumer)
+      return;
+    consumer->error_callback_ = cb;
+  });
+}
+
 // Can be called from any thread.
 void TracingMuxerImpl::TracingSessionImpl::SetOnStopCallback(
     std::function<void()> cb) {
@@ -436,6 +602,16 @@
   });
 }
 
+// Can be called from any thread.
+void TracingMuxerImpl::TracingSessionImpl::QueryServiceState(
+    QueryServiceStateCallback cb) {
+  auto* muxer = muxer_;
+  auto session_id = session_id_;
+  muxer->task_runner_->PostTask([muxer, session_id, cb] {
+    muxer->QueryServiceState(session_id, std::move(cb));
+  });
+}
+
 // ----- End of TracingMuxerImpl::TracingSessionImpl
 
 // static
@@ -472,14 +648,16 @@
     rb.backend = backend;
     rb.id = backend_id;
     rb.type = type;
-    rb.producer.reset(new ProducerImpl(this, backend_id));
-    TracingBackend::ConnectProducerArgs conn_args;
-    conn_args.producer = rb.producer.get();
-    conn_args.producer_name = platform_->GetCurrentProcessName();
-    conn_args.task_runner = task_runner_.get();
-    conn_args.shmem_size_hint_bytes = args.shmem_size_hint_kb * 1024;
-    conn_args.shmem_page_size_hint_bytes = args.shmem_page_size_hint_kb * 1024;
-    rb.producer->Initialize(rb.backend->ConnectProducer(conn_args));
+    rb.producer.reset(new ProducerImpl(this, backend_id,
+                                       args.shmem_batch_commits_duration_ms));
+    rb.producer_conn_args.producer = rb.producer.get();
+    rb.producer_conn_args.producer_name = platform_->GetCurrentProcessName();
+    rb.producer_conn_args.task_runner = task_runner_.get();
+    rb.producer_conn_args.shmem_size_hint_bytes =
+        args.shmem_size_hint_kb * 1024;
+    rb.producer_conn_args.shmem_page_size_hint_bytes =
+        args.shmem_page_size_hint_kb * 1024;
+    rb.producer->Initialize(rb.backend->ConnectProducer(rb.producer_conn_args));
   };
 
   if (args.backends & kSystemBackend) {
@@ -538,8 +716,44 @@
   return true;
 }
 
+// Can be called from any thread (but not concurrently).
+void TracingMuxerImpl::RegisterInterceptor(
+    const InterceptorDescriptor& descriptor,
+    InterceptorFactory factory,
+    InterceptorBase::TLSFactory tls_factory,
+    InterceptorBase::TracePacketCallback packet_callback) {
+  task_runner_->PostTask(
+      [this, descriptor, factory, tls_factory, packet_callback] {
+        // Ignore repeated registrations.
+        for (const auto& interceptor : interceptors_) {
+          if (interceptor.descriptor.name() == descriptor.name()) {
+            PERFETTO_DCHECK(interceptor.tls_factory == tls_factory);
+            PERFETTO_DCHECK(interceptor.packet_callback == packet_callback);
+            return;
+          }
+        }
+        // Only allow certain interceptors for now.
+        if (descriptor.name() != "test_interceptor" &&
+            descriptor.name() != "console") {
+          PERFETTO_ELOG(
+              "Interceptors are experimental. If you want to use them, please "
+              "get in touch with the project maintainers "
+              "(https://perfetto.dev/docs/contributing/"
+              "getting-started#community).");
+          return;
+        }
+        interceptors_.emplace_back();
+        RegisteredInterceptor& interceptor = interceptors_.back();
+        interceptor.descriptor = descriptor;
+        interceptor.factory = factory;
+        interceptor.tls_factory = tls_factory;
+        interceptor.packet_callback = packet_callback;
+      });
+}
+
 // Called by the service of one of the backends.
 void TracingMuxerImpl::SetupDataSource(TracingBackendId backend_id,
+                                       uint32_t backend_connection_id,
                                        DataSourceInstanceID instance_id,
                                        const DataSourceConfig& cfg) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
@@ -591,11 +805,34 @@
                        DataSourceInstanceID>::value,
           "data_source_instance_id type mismatch");
       internal_state->backend_id = backend_id;
+      internal_state->backend_connection_id = backend_connection_id;
       internal_state->data_source_instance_id = instance_id;
       internal_state->buffer_id =
           static_cast<internal::BufferId>(cfg.target_buffer());
       internal_state->config_hash = config_hash;
       internal_state->data_source = rds.factory();
+      internal_state->interceptor = nullptr;
+      internal_state->interceptor_id = 0;
+
+      if (cfg.has_interceptor_config()) {
+        for (size_t j = 0; j < interceptors_.size(); j++) {
+          if (cfg.interceptor_config().name() ==
+              interceptors_[j].descriptor.name()) {
+            PERFETTO_DLOG("Intercepting data source %" PRIu64
+                          " \"%s\" into \"%s\"",
+                          instance_id, cfg.name().c_str(),
+                          cfg.interceptor_config().name().c_str());
+            internal_state->interceptor_id = static_cast<uint32_t>(j + 1);
+            internal_state->interceptor = interceptors_[j].factory();
+            internal_state->interceptor->OnSetup({cfg});
+            break;
+          }
+        }
+        if (!internal_state->interceptor_id) {
+          PERFETTO_ELOG("Unknown interceptor configured for data source: %s",
+                        cfg.interceptor_config().name().c_str());
+        }
+      }
 
       // This must be made at the end. See matching acquire-load in
       // DataSource::Trace().
@@ -631,6 +868,8 @@
   start_args.internal_instance_index = ds.instance_idx;
 
   std::lock_guard<std::recursive_mutex> guard(ds.internal_state->lock);
+  if (ds.internal_state->interceptor)
+    ds.internal_state->interceptor->OnStart({});
   ds.internal_state->trace_lambda_enabled = true;
   ds.internal_state->data_source->OnStart(start_args);
 }
@@ -663,6 +902,8 @@
 
   {
     std::lock_guard<std::recursive_mutex> guard(ds.internal_state->lock);
+    if (ds.internal_state->interceptor)
+      ds.internal_state->interceptor->OnStop({});
     ds.internal_state->data_source->OnStop(stop_args);
   }
 
@@ -709,8 +950,69 @@
   // |backends_| is append-only, Backend instances are always valid.
   PERFETTO_CHECK(backend_id < backends_.size());
   ProducerImpl* producer = backends_[backend_id].producer.get();
-  if (producer && producer->connected_)
+  if (!producer)
+    return;
+  if (producer->connected_) {
+    // Flush any commits that might have been batched by SharedMemoryArbiter.
+    producer->service_->MaybeSharedMemoryArbiter()
+        ->FlushPendingCommitDataRequests();
     producer->service_->NotifyDataSourceStopped(instance_id);
+  }
+  producer->SweepDeadServices();
+}
+
+void TracingMuxerImpl::SyncProducersForTesting() {
+  std::mutex mutex;
+  std::condition_variable cv;
+
+  // IPC-based producers don't report connection errors explicitly for each
+  // command, but instead with an asynchronous callback
+  // (ProducerImpl::OnDisconnected). This means that the sync command below
+  // may have completed but failed to reach the service because of a
+  // disconnection, but we can't tell until the disconnection message comes
+  // through. To guard against this, we run two whole rounds of sync round-trips
+  // before returning; the first one will detect any disconnected producers and
+  // the second one will ensure any reconnections have completed and all data
+  // sources are registered in the service again.
+  for (size_t i = 0; i < 2; i++) {
+    size_t countdown = std::numeric_limits<size_t>::max();
+    task_runner_->PostTask([this, &mutex, &cv, &countdown] {
+      {
+        std::unique_lock<std::mutex> countdown_lock(mutex);
+        countdown = backends_.size();
+      }
+      for (auto& backend : backends_) {
+        auto* producer = backend.producer.get();
+        producer->service_->Sync([&mutex, &cv, &countdown] {
+          std::unique_lock<std::mutex> countdown_lock(mutex);
+          countdown--;
+          cv.notify_one();
+        });
+      }
+    });
+
+    {
+      std::unique_lock<std::mutex> countdown_lock(mutex);
+      cv.wait(countdown_lock, [&countdown] { return !countdown; });
+    }
+  }
+
+  // Check that all producers are indeed connected.
+  bool done = false;
+  bool all_producers_connected = true;
+  task_runner_->PostTask([this, &mutex, &cv, &done, &all_producers_connected] {
+    for (auto& backend : backends_)
+      all_producers_connected &= backend.producer->connected_;
+    std::unique_lock<std::mutex> lock(mutex);
+    done = true;
+    cv.notify_one();
+  });
+
+  {
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [&done] { return done; });
+  }
+  PERFETTO_DCHECK(all_producers_connected);
 }
 
 void TracingMuxerImpl::DestroyStoppedTraceWritersForCurrentThread() {
@@ -732,7 +1034,9 @@
 
       DataSourceState* ds_state = static_state->TryGet(inst);
       if (ds_state && ds_state->backend_id == ds_tls.backend_id &&
-          ds_state->buffer_id == ds_tls.buffer_id) {
+          ds_state->backend_connection_id == ds_tls.backend_connection_id &&
+          ds_state->buffer_id == ds_tls.buffer_id &&
+          ds_state->data_source_instance_id == ds_tls.data_source_instance_id) {
         continue;
       }
 
@@ -761,7 +1065,7 @@
       if (!backend.producer->connected_)
         continue;
 
-      PERFETTO_DCHECK(rds.static_state->index < kMaxDataSourceInstances);
+      PERFETTO_DCHECK(rds.static_state->index < kMaxDataSources);
       if (backend.producer->registered_data_sources_.test(
               rds.static_state->index))
         continue;
@@ -828,6 +1132,42 @@
   // TODO implement support for the deferred-start + fast-triggering case.
 }
 
+void TracingMuxerImpl::ChangeTracingSessionConfig(
+    TracingSessionGlobalID session_id,
+    const TraceConfig& trace_config) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+
+  auto* consumer = FindConsumer(session_id);
+
+  if (!consumer)
+    return;
+
+  if (!consumer->trace_config_) {
+    // Changing the config is only supported for started sessions.
+    PERFETTO_ELOG("Must call Setup(config) and Start() first");
+    return;
+  }
+
+  consumer->trace_config_ = std::make_shared<TraceConfig>(trace_config);
+  if (consumer->connected_)
+    consumer->service_->ChangeTraceConfig(trace_config);
+}
+
+void TracingMuxerImpl::FlushTracingSession(TracingSessionGlobalID session_id,
+                                           uint32_t timeout_ms,
+                                           std::function<void(bool)> callback) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  auto* consumer = FindConsumer(session_id);
+  if (!consumer || consumer->start_pending_ || consumer->stop_pending_ ||
+      !consumer->trace_config_) {
+    PERFETTO_ELOG("Flush() can be called only after Start() and before Stop()");
+    std::move(callback)(false);
+    return;
+  }
+
+  consumer->service_->Flush(timeout_ms, std::move(callback));
+}
+
 void TracingMuxerImpl::StopTracingSession(TracingSessionGlobalID session_id) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   auto* consumer = FindConsumer(session_id);
@@ -890,8 +1230,12 @@
     std::function<void(TracingSession::ReadTraceCallbackArgs)> callback) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   auto* consumer = FindConsumer(session_id);
-  if (!consumer)
+  if (!consumer) {
+    // TODO(skyostil): Signal an error to the user.
+    TracingSession::ReadTraceCallbackArgs callback_arg{};
+    callback(callback_arg);
     return;
+  }
   PERFETTO_DCHECK(!consumer->read_trace_callback_);
   consumer->read_trace_callback_ = std::move(callback);
   consumer->service_->ReadBuffers();
@@ -918,6 +1262,57 @@
   consumer->service_->GetTraceStats();
 }
 
+void TracingMuxerImpl::QueryServiceState(
+    TracingSessionGlobalID session_id,
+    TracingSession::QueryServiceStateCallback callback) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  auto* consumer = FindConsumer(session_id);
+  if (!consumer) {
+    TracingSession::QueryServiceStateCallbackArgs callback_arg{};
+    callback_arg.success = false;
+    callback(std::move(callback_arg));
+    return;
+  }
+  PERFETTO_DCHECK(!consumer->query_service_state_callback_);
+  if (!consumer->connected_) {
+    consumer->query_service_state_callback_ = std::move(callback);
+    return;
+  }
+  auto callback_wrapper = [callback](bool success,
+                                     protos::gen::TracingServiceState state) {
+    TracingSession::QueryServiceStateCallbackArgs callback_arg{};
+    callback_arg.success = success;
+    callback_arg.service_state_data = state.SerializeAsArray();
+    callback(std::move(callback_arg));
+  };
+  consumer->service_->QueryServiceState(std::move(callback_wrapper));
+}
+
+void TracingMuxerImpl::SetBatchCommitsDurationForTesting(
+    uint32_t batch_commits_duration_ms,
+    BackendType backend_type) {
+  for (RegisteredBackend& backend : backends_) {
+    if (backend.producer && backend.producer->connected_ &&
+        backend.type == backend_type) {
+      backend.producer->service_->MaybeSharedMemoryArbiter()
+          ->SetBatchCommitsDuration(batch_commits_duration_ms);
+    }
+  }
+}
+
+bool TracingMuxerImpl::EnableDirectSMBPatchingForTesting(
+    BackendType backend_type) {
+  for (RegisteredBackend& backend : backends_) {
+    if (backend.producer && backend.producer->connected_ &&
+        backend.type == backend_type &&
+        !backend.producer->service_->MaybeSharedMemoryArbiter()
+             ->EnableDirectSMBPatching()) {
+      return false;
+    }
+  }
+  return true;
+}
+
 TracingMuxerImpl::ConsumerImpl* TracingMuxerImpl::FindConsumer(
     TracingSessionGlobalID session_id) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
@@ -944,6 +1339,36 @@
   }
 }
 
+void TracingMuxerImpl::SetMaxProducerReconnectionsForTesting(uint32_t count) {
+  max_producer_reconnections_.store(count);
+}
+
+void TracingMuxerImpl::OnProducerDisconnected(ProducerImpl* producer) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  for (RegisteredBackend& backend : backends_) {
+    if (backend.producer.get() != producer)
+      continue;
+    // Try reconnecting the disconnected producer. If the connection succeeds,
+    // all the data sources will be automatically re-registered.
+    if (producer->connection_id_ > max_producer_reconnections_.load()) {
+      // Avoid reconnecting a failing producer too many times. Instead we just
+      // leak the producer instead of trying to avoid further complicating
+      // cross-thread trace writer creation.
+      PERFETTO_ELOG("Producer disconnected too many times; not reconnecting");
+      continue;
+    }
+    backend.producer->Initialize(
+        backend.backend->ConnectProducer(backend.producer_conn_args));
+  }
+
+  // Increment the generation counter to atomically ensure that:
+  // 1. Old trace writers from the severed connection eventually get cleaned up
+  //    by DestroyStoppedTraceWritersForCurrentThread().
+  // 2. No new trace writers can be created for the SharedMemoryArbiter from the
+  //    old connection.
+  TracingMuxer::generation_++;
+}
+
 TracingMuxerImpl::FindDataSourceRes TracingMuxerImpl::FindDataSource(
     TracingBackendId backend_id,
     DataSourceInstanceID instance_id) {
@@ -963,11 +1388,40 @@
 
 // Can be called from any thread.
 std::unique_ptr<TraceWriterBase> TracingMuxerImpl::CreateTraceWriter(
+    DataSourceStaticState* static_state,
+    uint32_t data_source_instance_index,
     DataSourceState* data_source,
     BufferExhaustedPolicy buffer_exhausted_policy) {
+  if (PERFETTO_UNLIKELY(data_source->interceptor_id)) {
+    // If the session is being intercepted, return a heap-backed trace writer
+    // instead. This is safe because all the data given to the interceptor is
+    // either thread-local (|instance_index|), statically allocated
+    // (|static_state|) or constant after initialization (|interceptor|). Access
+    // to the interceptor instance itself through |data_source| is protected by
+    // a statically allocated lock (similarly to the data source instance).
+    auto& interceptor = interceptors_[data_source->interceptor_id - 1];
+    return std::unique_ptr<TraceWriterBase>(new InterceptorTraceWriter(
+        interceptor.tls_factory(static_state, data_source_instance_index),
+        interceptor.packet_callback, static_state, data_source_instance_index));
+  }
   ProducerImpl* producer = backends_[data_source->backend_id].producer.get();
-  return producer->service_->CreateTraceWriter(data_source->buffer_id,
-                                               buffer_exhausted_policy);
+  // Atomically load the current service endpoint. We keep the pointer as a
+  // shared pointer on the stack to guard against it from being concurrently
+  // modified on the thread by ProducerImpl::Initialize() swapping in a
+  // reconnected service on the muxer task runner thread.
+  //
+  // The endpoint may also be concurrently modified by SweepDeadServices()
+  // clearing out old disconnected services. We guard against that by
+  // SharedMemoryArbiter keeping track of any outstanding trace writers. After
+  // shutdown has started, the trace writer created below will be a null one
+  // which will drop any written data. See SharedMemoryArbiter::TryShutdown().
+  //
+  // We use an atomic pointer instead of holding a lock because
+  // CreateTraceWriter posts tasks under the hood.
+  std::shared_ptr<ProducerEndpoint> service =
+      std::atomic_load(&producer->service_);
+  return service->CreateTraceWriter(data_source->buffer_id,
+                                    buffer_exhausted_policy);
 }
 
 // This is called via the public API Tracing::NewTrace().
@@ -986,7 +1440,7 @@
         continue;
 
       backend.consumers.emplace_back(
-          new ConsumerImpl(this, backend.id, session_id));
+          new ConsumerImpl(this, backend.type, backend.id, session_id));
       auto& consumer = backend.consumers.back();
       TracingBackend::ConnectConsumerArgs conn_args;
       conn_args.consumer = consumer.get();
diff --git a/src/tracing/internal/tracing_muxer_impl.h b/src/tracing/internal/tracing_muxer_impl.h
index 477a6a4..d1fa4ee 100644
--- a/src/tracing/internal/tracing_muxer_impl.h
+++ b/src/tracing/internal/tracing_muxer_impl.h
@@ -23,6 +23,7 @@
 #include <array>
 #include <atomic>
 #include <bitset>
+#include <list>
 #include <map>
 #include <memory>
 #include <vector>
@@ -38,6 +39,9 @@
 #include "perfetto/tracing/internal/basic_types.h"
 #include "perfetto/tracing/internal/tracing_muxer.h"
 #include "perfetto/tracing/tracing.h"
+
+#include "protos/perfetto/common/interceptor_descriptor.gen.h"
+
 namespace perfetto {
 
 class ConsumerEndpoint;
@@ -99,33 +103,60 @@
                           DataSourceFactory,
                           DataSourceStaticState*) override;
   std::unique_ptr<TraceWriterBase> CreateTraceWriter(
+      DataSourceStaticState*,
+      uint32_t data_source_instance_index,
       DataSourceState*,
       BufferExhaustedPolicy buffer_exhausted_policy) override;
   void DestroyStoppedTraceWritersForCurrentThread() override;
+  void RegisterInterceptor(const InterceptorDescriptor&,
+                           InterceptorFactory,
+                           InterceptorBase::TLSFactory,
+                           InterceptorBase::TracePacketCallback) override;
 
   std::unique_ptr<TracingSession> CreateTracingSession(BackendType);
 
   // Producer-side bookkeeping methods.
   void UpdateDataSourcesOnAllBackends();
   void SetupDataSource(TracingBackendId,
+                       uint32_t backend_connection_id,
                        DataSourceInstanceID,
                        const DataSourceConfig&);
   void StartDataSource(TracingBackendId, DataSourceInstanceID);
   void StopDataSource_AsyncBegin(TracingBackendId, DataSourceInstanceID);
   void StopDataSource_AsyncEnd(TracingBackendId, DataSourceInstanceID);
+  void SyncProducersForTesting();
 
   // Consumer-side bookkeeping methods.
   void SetupTracingSession(TracingSessionGlobalID,
                            const std::shared_ptr<TraceConfig>&,
                            base::ScopedFile trace_fd = base::ScopedFile());
   void StartTracingSession(TracingSessionGlobalID);
+  void ChangeTracingSessionConfig(TracingSessionGlobalID, const TraceConfig&);
   void StopTracingSession(TracingSessionGlobalID);
   void DestroyTracingSession(TracingSessionGlobalID);
+  void FlushTracingSession(TracingSessionGlobalID,
+                           uint32_t,
+                           std::function<void(bool)>);
   void ReadTracingSessionData(
       TracingSessionGlobalID,
       std::function<void(TracingSession::ReadTraceCallbackArgs)>);
   void GetTraceStats(TracingSessionGlobalID,
                      TracingSession::GetTraceStatsCallback);
+  void QueryServiceState(TracingSessionGlobalID,
+                         TracingSession::QueryServiceStateCallback);
+
+  // Sets the batching period to |batch_commits_duration_ms| on the backends
+  // with type |backend_type|.
+  void SetBatchCommitsDurationForTesting(uint32_t batch_commits_duration_ms,
+                                         BackendType backend_type);
+
+  // Enables direct SMB patching on the backends with type |backend_type| (see
+  // SharedMemoryArbiter::EnableDirectSMBPatching). Returns true if the
+  // operation succeeded for all backends with type |backend_type|, false
+  // otherwise.
+  bool EnableDirectSMBPatchingForTesting(BackendType backend_type);
+
+  void SetMaxProducerReconnectionsForTesting(uint32_t count);
 
  private:
   // For each TracingBackend we create and register one ProducerImpl instance.
@@ -137,7 +168,9 @@
   // because the Producer virtual methods don't allow to identify the service.
   class ProducerImpl : public Producer {
    public:
-    ProducerImpl(TracingMuxerImpl*, TracingBackendId);
+    ProducerImpl(TracingMuxerImpl*,
+                 TracingBackendId,
+                 uint32_t shmem_batch_commits_duration_ms);
     ~ProducerImpl() override;
 
     void Initialize(std::unique_ptr<ProducerEndpoint> endpoint);
@@ -157,17 +190,38 @@
     void Flush(FlushRequestID, const DataSourceInstanceID*, size_t) override;
     void ClearIncrementalState(const DataSourceInstanceID*, size_t) override;
 
+    void SweepDeadServices();
+
     PERFETTO_THREAD_CHECKER(thread_checker_)
     TracingMuxerImpl* const muxer_;
     TracingBackendId const backend_id_;
     bool connected_ = false;
+    uint32_t connection_id_ = 0;
+
+    const uint32_t shmem_batch_commits_duration_ms_ = 0;
 
     // Set of data sources that have been actually registered on this producer.
     // This can be a subset of the global |data_sources_|, because data sources
     // can register before the producer is fully connected.
     std::bitset<kMaxDataSources> registered_data_sources_{};
 
-    std::unique_ptr<ProducerEndpoint> service_;  // Keep last.
+    // A collection of disconnected service endpoints. Since trace writers on
+    // arbitrary threads might continue writing data to disconnected services,
+    // we keep the old services around and periodically try to clean up ones
+    // that no longer have any writers (see SweepDeadServices).
+    std::list<std::shared_ptr<ProducerEndpoint>> dead_services_;
+
+    // The currently active service endpoint is maintained as an atomic shared
+    // pointer so it won't get deleted from underneath threads that are creating
+    // trace writers. At any given time one endpoint can be shared (and thus
+    // kept alive) by the |service_| pointer, an entry in |dead_services_| and
+    // as a pointer on the stack in CreateTraceWriter() (on an arbitrary
+    // thread). The endpoint is never shared outside ProducerImpl itself.
+    //
+    // WARNING: Any *write* access to this variable or any *read* access from a
+    // non-muxer thread must be done through std::atomic_{load,store} to avoid
+    // data races.
+    std::shared_ptr<ProducerEndpoint> service_;  // Keep last.
   };
 
   // For each TracingSession created by the API client (Tracing::NewTrace() we
@@ -178,7 +232,10 @@
   // tracing sessions.
   class ConsumerImpl : public Consumer {
    public:
-    ConsumerImpl(TracingMuxerImpl*, TracingBackendId, TracingSessionGlobalID);
+    ConsumerImpl(TracingMuxerImpl*,
+                 BackendType,
+                 TracingBackendId,
+                 TracingSessionGlobalID);
     ~ConsumerImpl() override;
 
     void Initialize(std::unique_ptr<ConsumerEndpoint> endpoint);
@@ -186,7 +243,7 @@
     // perfetto::Consumer implementation.
     void OnConnect() override;
     void OnDisconnect() override;
-    void OnTracingDisabled() override;
+    void OnTracingDisabled(const std::string& error) override;
     void OnTraceData(std::vector<TracePacket>, bool has_more) override;
     void OnDetach(bool success) override;
     void OnAttach(bool success, const TraceConfig&) override;
@@ -194,12 +251,14 @@
     void OnObservableEvents(const ObservableEvents&) override;
 
     void NotifyStartComplete();
+    void NotifyError(const TracingError&);
     void NotifyStopComplete();
 
     // Will eventually inform the |muxer_| when it is safe to remove |this|.
     void Disconnect();
 
     TracingMuxerImpl* const muxer_;
+    BackendType const backend_type_;
     TracingBackendId const backend_id_;
     TracingSessionGlobalID const session_id_;
     bool connected_ = false;
@@ -234,6 +293,10 @@
     // An internal callback used to implement StartBlocking().
     std::function<void()> blocking_start_complete_callback_;
 
+    // If the API client passes a callback to get notification about the
+    // errors, we should invoke this when NotifyError() is invoked.
+    std::function<void(TracingError)> error_callback_;
+
     // If the API client passes a callback to stop, we should invoke this when
     // OnTracingDisabled() is invoked.
     std::function<void()> stop_complete_callback_;
@@ -248,6 +311,9 @@
     // Callback passed to GetTraceStats().
     TracingSession::GetTraceStatsCallback get_trace_stats_callback_;
 
+    // Callback for a pending call to QueryServiceState().
+    TracingSession::QueryServiceStateCallback query_service_state_callback_;
+
     // The states of all data sources in this tracing session. |true| means the
     // data source has started tracing.
     using DataSourceHandle = std::pair<std::string, std::string>;
@@ -267,11 +333,15 @@
     void Start() override;
     void StartBlocking() override;
     void SetOnStartCallback(std::function<void()>) override;
+    void SetOnErrorCallback(std::function<void(TracingError)>) override;
     void Stop() override;
     void StopBlocking() override;
+    void Flush(std::function<void(bool)>, uint32_t timeout_ms) override;
     void ReadTrace(ReadTraceCallback) override;
     void SetOnStopCallback(std::function<void()>) override;
     void GetTraceStats(GetTraceStatsCallback) override;
+    void QueryServiceState(QueryServiceStateCallback) override;
+    void ChangeTraceConfig(const TraceConfig&) override;
 
    private:
     TracingMuxerImpl* const muxer_;
@@ -284,12 +354,20 @@
     DataSourceStaticState* static_state = nullptr;
   };
 
+  struct RegisteredInterceptor {
+    protos::gen::InterceptorDescriptor descriptor;
+    InterceptorFactory factory{};
+    InterceptorBase::TLSFactory tls_factory{};
+    InterceptorBase::TracePacketCallback packet_callback{};
+  };
+
   struct RegisteredBackend {
     // Backends are supposed to have static lifetime.
     TracingBackend* backend = nullptr;
     TracingBackendId id = 0;
     BackendType type{};
 
+    TracingBackend::ConnectProducerArgs producer_conn_args;
     std::unique_ptr<ProducerImpl> producer;
 
     // The calling code can request more than one concurrently active tracing
@@ -301,6 +379,7 @@
   void Initialize(const TracingInitArgs& args);
   ConsumerImpl* FindConsumer(TracingSessionGlobalID session_id);
   void OnConsumerDisconnected(ConsumerImpl* consumer);
+  void OnProducerDisconnected(ProducerImpl* producer);
 
   struct FindDataSourceRes {
     FindDataSourceRes() = default;
@@ -317,9 +396,14 @@
   std::unique_ptr<base::TaskRunner> task_runner_;
   std::vector<RegisteredDataSource> data_sources_;
   std::vector<RegisteredBackend> backends_;
+  std::vector<RegisteredInterceptor> interceptors_;
 
   std::atomic<TracingSessionGlobalID> next_tracing_session_id_{};
 
+  // Maximum number of times we will try to reconnect producer backend.
+  // Should only be modified for testing purposes.
+  std::atomic<uint32_t> max_producer_reconnections_{100u};
+
   PERFETTO_THREAD_CHECKER(thread_checker_)
 };
 
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index 94c2a77..77ecf18 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -31,6 +31,12 @@
 #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 
 namespace perfetto {
+
+TrackEventSessionObserver::~TrackEventSessionObserver() = default;
+void TrackEventSessionObserver::OnSetup(const DataSourceBase::SetupArgs&) {}
+void TrackEventSessionObserver::OnStart(const DataSourceBase::StartArgs&) {}
+void TrackEventSessionObserver::OnStop(const DataSourceBase::StopArgs&) {}
+
 namespace internal {
 
 BaseTrackEventInternedDataIndex::~BaseTrackEventInternedDataIndex() = default;
@@ -42,6 +48,19 @@
 static constexpr const char kSlowTag[] = "slow";
 static constexpr const char kDebugTag[] = "debug";
 
+void ForEachObserver(
+    std::function<bool(TrackEventSessionObserver*&)> callback) {
+  // Session observers, shared by all track event data source instances.
+  static constexpr int kMaxObservers = 8;
+  static std::recursive_mutex* mutex = new std::recursive_mutex{};  // Leaked.
+  static std::array<TrackEventSessionObserver*, kMaxObservers> observers{};
+  std::unique_lock<std::recursive_mutex> lock(*mutex);
+  for (auto& o : observers) {
+    if (!callback(o))
+      break;
+  }
+}
+
 struct InternedEventCategory
     : public TrackEventInternedDataIndex<
           InternedEventCategory,
@@ -152,22 +171,68 @@
 }
 
 // static
+bool TrackEventInternal::AddSessionObserver(
+    TrackEventSessionObserver* observer) {
+  bool result = false;
+  ForEachObserver([&](TrackEventSessionObserver*& o) {
+    if (!o) {
+      o = observer;
+      result = true;
+      return false;
+    }
+    return true;
+  });
+  return result;
+}
+
+// static
+void TrackEventInternal::RemoveSessionObserver(
+    TrackEventSessionObserver* observer) {
+  ForEachObserver([&](TrackEventSessionObserver*& o) {
+    if (o == observer) {
+      o = nullptr;
+      return false;
+    }
+    return true;
+  });
+}
+
+// static
 void TrackEventInternal::EnableTracing(
     const TrackEventCategoryRegistry& registry,
     const protos::gen::TrackEventConfig& config,
-    uint32_t instance_index) {
+    const DataSourceBase::SetupArgs& args) {
   for (size_t i = 0; i < registry.category_count(); i++) {
     if (IsCategoryEnabled(registry, config, *registry.GetCategory(i)))
-      registry.EnableCategoryForInstance(i, instance_index);
+      registry.EnableCategoryForInstance(i, args.internal_instance_index);
   }
+  ForEachObserver([&](TrackEventSessionObserver*& o) {
+    if (o)
+      o->OnSetup(args);
+    return true;
+  });
+}
+
+// static
+void TrackEventInternal::OnStart(const DataSourceBase::StartArgs& args) {
+  ForEachObserver([&](TrackEventSessionObserver*& o) {
+    if (o)
+      o->OnStart(args);
+    return true;
+  });
 }
 
 // static
 void TrackEventInternal::DisableTracing(
     const TrackEventCategoryRegistry& registry,
-    uint32_t instance_index) {
+    const DataSourceBase::StopArgs& args) {
+  ForEachObserver([&](TrackEventSessionObserver*& o) {
+    if (o)
+      o->OnStop(args);
+    return true;
+  });
   for (size_t i = 0; i < registry.category_count(); i++)
-    registry.DisableCategoryForInstance(i, instance_index);
+    registry.DisableCategoryForInstance(i, args.internal_instance_index);
 }
 
 // static
diff --git a/src/tracing/ipc/consumer/BUILD.gn b/src/tracing/ipc/consumer/BUILD.gn
index 0a2dcc2..03c9559 100644
--- a/src/tracing/ipc/consumer/BUILD.gn
+++ b/src/tracing/ipc/consumer/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_component.gni")
 
 assert(enable_perfetto_ipc)
 
@@ -32,6 +33,10 @@
     "..:common",
     "../../../../gn:default_deps",
     "../../../base",
-    "../../../ipc:client",
   ]
+  if (perfetto_component_type == "static_library") {
+    deps += [ "../../../ipc:perfetto_ipc" ]
+  } else {
+    deps += [ "../../../ipc:client" ]
+  }
 }
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
index 7d60e5e..59aeaaf 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
@@ -46,7 +46,9 @@
                                              Consumer* consumer,
                                              base::TaskRunner* task_runner)
     : consumer_(consumer),
-      ipc_channel_(ipc::Client::CreateInstance(service_sock_name, task_runner)),
+      ipc_channel_(
+          ipc::Client::CreateInstance({service_sock_name, /*retry=*/false},
+                                      task_runner)),
       consumer_port_(this /* event_listener */),
       weak_ptr_factory_(this) {
   ipc_channel_->BindService(consumer_port_.GetWeakPtr());
@@ -63,7 +65,7 @@
 void ConsumerIPCClientImpl::OnDisconnect() {
   PERFETTO_DLOG("Tracing service connection failure");
   connected_ = false;
-  consumer_->OnDisconnect();
+  consumer_->OnDisconnect();  // Note: may delete |this|.
 }
 
 void ConsumerIPCClientImpl::EnableTracing(const TraceConfig& trace_config,
@@ -89,7 +91,7 @@
   consumer_port_.EnableTracing(req, std::move(async_response), *fd);
 }
 
-void ConsumerIPCClientImpl::ChangeTraceConfig(const TraceConfig&) {
+void ConsumerIPCClientImpl::ChangeTraceConfig(const TraceConfig& trace_config) {
   if (!connected_) {
     PERFETTO_DLOG(
         "Cannot ChangeTraceConfig(), not connected to tracing service");
@@ -103,6 +105,7 @@
           PERFETTO_DLOG("ChangeTraceConfig() failed");
       });
   protos::gen::ChangeTraceConfigRequest req;
+  *req.mutable_trace_config() = trace_config;
   consumer_port_.ChangeTraceConfig(req, std::move(async_response));
 }
 
@@ -179,8 +182,18 @@
 
 void ConsumerIPCClientImpl::OnEnableTracingResponse(
     ipc::AsyncResult<protos::gen::EnableTracingResponse> response) {
+  std::string error;
+  // |response| might be empty when the request gets rejected (if the connection
+  // with the service is dropped all outstanding requests are auto-rejected).
+  if (!response) {
+    error =
+        "EnableTracing IPC request rejected. This is likely due to a loss of "
+        "the traced connection";
+  } else {
+    error = response->error();
+  }
   if (!response || response->disabled())
-    consumer_->OnTracingDisabled();
+    consumer_->OnTracingDisabled(error);
 }
 
 void ConsumerIPCClientImpl::FreeBuffers() {
@@ -322,7 +335,7 @@
       [this](ipc::AsyncResult<protos::gen::ObserveEventsResponse> response) {
         // Skip empty response, which the service sends to close the stream.
         if (!response.has_more()) {
-          PERFETTO_DCHECK(!response->events().instance_state_changes().size());
+          PERFETTO_DCHECK(!response.success());
           return;
         }
         consumer_->OnObservableEvents(response->events());
@@ -411,4 +424,30 @@
   consumer_port_.QueryCapabilities(req, std::move(async_response));
 }
 
+void ConsumerIPCClientImpl::SaveTraceForBugreport(
+    SaveTraceForBugreportCallback callback) {
+  if (!connected_) {
+    PERFETTO_DLOG(
+        "Cannot SaveTraceForBugreport(), not connected to tracing service");
+    return;
+  }
+
+  protos::gen::SaveTraceForBugreportRequest req;
+  ipc::Deferred<protos::gen::SaveTraceForBugreportResponse> async_response;
+  async_response.Bind(
+      [callback](ipc::AsyncResult<protos::gen::SaveTraceForBugreportResponse>
+                     response) {
+        if (!response) {
+          // If the IPC fails, we are talking to an older version of the service
+          // that didn't support SaveTraceForBugreport at all.
+          callback(
+              false,
+              "The tracing service doesn't support SaveTraceForBugreport()");
+        } else {
+          callback(response->success(), response->msg());
+        }
+      });
+  consumer_port_.SaveTraceForBugreport(req, std::move(async_response));
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
index ff305f6..7d7d6fa 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.h
@@ -73,6 +73,7 @@
   void ObserveEvents(uint32_t enabled_event_types) override;
   void QueryServiceState(QueryServiceStateCallback) override;
   void QueryCapabilities(QueryCapabilitiesCallback) override;
+  void SaveTraceForBugreport(SaveTraceForBugreportCallback) override;
 
   // ipc::ServiceProxy::EventListener implementation.
   // These methods are invoked by the IPC layer, which knows nothing about
diff --git a/src/tracing/ipc/default_socket.cc b/src/tracing/ipc/default_socket.cc
index 519bb06..255af8d 100644
--- a/src/tracing/ipc/default_socket.cc
+++ b/src/tracing/ipc/default_socket.cc
@@ -17,34 +17,75 @@
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/ipc/basic_types.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 
 #include <stdlib.h>
+#include <unistd.h>
 
 namespace perfetto {
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+// On non-Android platforms, check /run/perfetto/ before using /tmp/ as the
+// socket base directory.
+namespace {
+const char* kRunPerfettoBaseDir = "/run/perfetto/";
+
+bool UseRunPerfettoBaseDir() {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  // Note that the trailing / in |kRunPerfettoBaseDir| ensures we are checking
+  // against a directory, not a file.
+  int res = PERFETTO_EINTR(access(kRunPerfettoBaseDir, X_OK));
+  if (!res)
+    return true;
+
+  // If the path doesn't exist (ENOENT), fail silently to the caller. Otherwise,
+  // fail with an explicit error message.
+  if (errno != ENOENT) {
+    PERFETTO_PLOG("%s exists but cannot be accessed. Falling back on /tmp/ ",
+                  kRunPerfettoBaseDir);
+  }
+  return false;
+#else
+  return false;
+#endif
+}
+
+}  // anonymous namespace
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
 
 static_assert(kInvalidUid == ipc::kInvalidUid, "kInvalidUid mismatching");
 
 const char* GetProducerSocket() {
-  static const char* name = getenv("PERFETTO_PRODUCER_SOCK_NAME");
+  const char* name = getenv("PERFETTO_PRODUCER_SOCK_NAME");
   if (name == nullptr) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
     name = "/dev/socket/traced_producer";
 #else
-    name = "/tmp/perfetto-producer";
+    // Use /run/perfetto if it exists. Then fallback to /tmp.
+    static const char* producer_socket =
+        UseRunPerfettoBaseDir() ? "/run/perfetto/traced-producer.sock"
+                                : "/tmp/perfetto-producer";
+    name = producer_socket;
 #endif
   }
   return name;
 }
 
 const char* GetConsumerSocket() {
-  static const char* name = getenv("PERFETTO_CONSUMER_SOCK_NAME");
+  const char* name = getenv("PERFETTO_CONSUMER_SOCK_NAME");
   if (name == nullptr) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
     name = "/dev/socket/traced_consumer";
 #else
-    name = "/tmp/perfetto-consumer";
+    // Use /run/perfetto if it exists. Then fallback to /tmp.
+    static const char* consumer_socket =
+        UseRunPerfettoBaseDir() ? "/run/perfetto/traced-consumer.sock"
+                                : "/tmp/perfetto-consumer";
+    name = consumer_socket;
 #endif
   }
   return name;
diff --git a/src/tracing/ipc/posix_shared_memory_unittest.cc b/src/tracing/ipc/posix_shared_memory_unittest.cc
index e5589d0..c9ab3e8 100644
--- a/src/tracing/ipc/posix_shared_memory_unittest.cc
+++ b/src/tracing/ipc/posix_shared_memory_unittest.cc
@@ -43,12 +43,12 @@
 TEST(PosixSharedMemoryTest, DestructorUnmapsMemory) {
   PosixSharedMemory::Factory factory;
   std::unique_ptr<SharedMemory> shm =
-      factory.CreateSharedMemory(base::kPageSize);
+      factory.CreateSharedMemory(base::GetSysPageSize());
   ASSERT_NE(shm.get(), nullptr);
   void* const shm_start = shm->start();
   const size_t shm_size = shm->size();
   ASSERT_NE(nullptr, shm_start);
-  ASSERT_EQ(base::kPageSize, shm_size);
+  ASSERT_EQ(base::GetSysPageSize(), shm_size);
 
   memcpy(shm_start, "test", 5);
   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
@@ -59,11 +59,11 @@
 
 TEST(PosixSharedMemoryTest, DestructorClosesFD) {
   std::unique_ptr<PosixSharedMemory> shm =
-      PosixSharedMemory::Create(base::kPageSize);
+      PosixSharedMemory::Create(base::GetSysPageSize());
   ASSERT_NE(shm.get(), nullptr);
   int fd = shm->fd();
   ASSERT_GE(fd, 0);
-  ASSERT_EQ(static_cast<off_t>(base::kPageSize), lseek(fd, 0, SEEK_END));
+  ASSERT_EQ(static_cast<off_t>(base::GetSysPageSize()), lseek(fd, 0, SEEK_END));
 
   shm.reset();
   ASSERT_TRUE(IsFileDescriptorClosed(fd));
@@ -72,7 +72,7 @@
 TEST(PosixSharedMemoryTest, AttachToFdWithoutSeals) {
   base::TempFile tmp_file = base::TempFile::CreateUnlinked();
   const int fd_num = tmp_file.fd();
-  ASSERT_EQ(0, ftruncate(fd_num, base::kPageSize));
+  ASSERT_EQ(0, ftruncate(fd_num, static_cast<off_t>(base::GetSysPageSize())));
   ASSERT_EQ(7, base::WriteAll(fd_num, "foobar", 7));
 
   std::unique_ptr<PosixSharedMemory> shm = PosixSharedMemory::AttachToFd(
@@ -81,7 +81,7 @@
   void* const shm_start = shm->start();
   const size_t shm_size = shm->size();
   ASSERT_NE(nullptr, shm_start);
-  ASSERT_EQ(base::kPageSize, shm_size);
+  ASSERT_EQ(base::GetSysPageSize(), shm_size);
   ASSERT_EQ(0, memcmp("foobar", shm_start, 7));
 
   ASSERT_FALSE(IsFileDescriptorClosed(fd_num));
@@ -94,7 +94,7 @@
 TEST(PosixSharedMemoryTest, AttachToFdRequiresSeals) {
   base::TempFile tmp_file = base::TempFile::CreateUnlinked();
   const int fd_num = tmp_file.fd();
-  ASSERT_EQ(0, ftruncate(fd_num, base::kPageSize));
+  ASSERT_EQ(0, ftruncate(fd_num, static_cast<off_t>(base::GetSysPageSize())));
 
   std::unique_ptr<PosixSharedMemory> shm =
       PosixSharedMemory::AttachToFd(tmp_file.ReleaseFD());
@@ -108,12 +108,15 @@
 }
 
 TEST(PosixSharedMemoryTest, CreateAndMap) {
+  // Deliberately trying to cover cases where the shm size is smaller than the
+  // system page size (crbug.com/1116576).
+  const size_t kLessThanAPage = 2048;
   std::unique_ptr<PosixSharedMemory> shm =
-      PosixSharedMemory::Create(base::kPageSize);
+      PosixSharedMemory::Create(kLessThanAPage);
   void* const shm_start = shm->start();
   const size_t shm_size = shm->size();
   ASSERT_NE(shm_start, nullptr);
-  ASSERT_EQ(shm_size, base::kPageSize);
+  ASSERT_EQ(shm_size, kLessThanAPage);
 
   memcpy(shm_start, "test", 5);
   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
diff --git a/src/tracing/ipc/producer/BUILD.gn b/src/tracing/ipc/producer/BUILD.gn
index c7e0d8a..8d37539 100644
--- a/src/tracing/ipc/producer/BUILD.gn
+++ b/src/tracing/ipc/producer/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_component.gni")
 
 assert(enable_perfetto_ipc)
 
@@ -32,6 +33,10 @@
     "..:common",
     "../../../../gn:default_deps",
     "../../../base",
-    "../../../ipc:client",
   ]
+  if (perfetto_component_type == "static_library") {
+    deps += [ "../../../ipc:perfetto_ipc" ]
+  } else {
+    deps += [ "../../../ipc:client" ]
+  }
 }
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index 58fc05d..ebb9e01 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -47,9 +47,31 @@
     size_t shared_memory_size_hint_bytes,
     size_t shared_memory_page_size_hint_bytes,
     std::unique_ptr<SharedMemory> shm,
+    std::unique_ptr<SharedMemoryArbiter> shm_arbiter,
+    ConnectionFlags conn_flags) {
+  return std::unique_ptr<TracingService::ProducerEndpoint>(
+      new ProducerIPCClientImpl(
+          {service_sock_name,
+           conn_flags ==
+               ProducerIPCClient::ConnectionFlags::kRetryIfUnreachable},
+          producer, producer_name, task_runner, smb_scraping_mode,
+          shared_memory_size_hint_bytes, shared_memory_page_size_hint_bytes,
+          std::move(shm), std::move(shm_arbiter)));
+}
+
+// static. (Declared in include/tracing/ipc/producer_ipc_client.h).
+std::unique_ptr<TracingService::ProducerEndpoint> ProducerIPCClient::Connect(
+    ipc::Client::ConnArgs conn_args,
+    Producer* producer,
+    const std::string& producer_name,
+    base::TaskRunner* task_runner,
+    TracingService::ProducerSMBScrapingMode smb_scraping_mode,
+    size_t shared_memory_size_hint_bytes,
+    size_t shared_memory_page_size_hint_bytes,
+    std::unique_ptr<SharedMemory> shm,
     std::unique_ptr<SharedMemoryArbiter> shm_arbiter) {
   return std::unique_ptr<TracingService::ProducerEndpoint>(
-      new ProducerIPCClientImpl(service_sock_name, producer, producer_name,
+      new ProducerIPCClientImpl(std::move(conn_args), producer, producer_name,
                                 task_runner, smb_scraping_mode,
                                 shared_memory_size_hint_bytes,
                                 shared_memory_page_size_hint_bytes,
@@ -57,7 +79,7 @@
 }
 
 ProducerIPCClientImpl::ProducerIPCClientImpl(
-    const char* service_sock_name,
+    ipc::Client::ConnArgs conn_args,
     Producer* producer,
     const std::string& producer_name,
     base::TaskRunner* task_runner,
@@ -68,7 +90,8 @@
     std::unique_ptr<SharedMemoryArbiter> shm_arbiter)
     : producer_(producer),
       task_runner_(task_runner),
-      ipc_channel_(ipc::Client::CreateInstance(service_sock_name, task_runner)),
+      ipc_channel_(
+          ipc::Client::CreateInstance(std::move(conn_args), task_runner)),
       producer_port_(this /* event_listener */),
       shared_memory_(std::move(shm)),
       shared_memory_arbiter_(std::move(shm_arbiter)),
@@ -92,7 +115,9 @@
   PERFETTO_DCHECK_THREAD(thread_checker_);
 }
 
-ProducerIPCClientImpl::~ProducerIPCClientImpl() = default;
+ProducerIPCClientImpl::~ProducerIPCClientImpl() {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+}
 
 // Called by the IPC layer if the BindService() succeeds.
 void ProducerIPCClientImpl::OnConnect() {
@@ -107,7 +132,8 @@
       [this](ipc::AsyncResult<protos::gen::InitializeConnectionResponse> resp) {
         OnConnectionInitialized(
             resp.success(),
-            resp.success() ? resp->using_shmem_provided_by_producer() : false);
+            resp.success() ? resp->using_shmem_provided_by_producer() : false,
+            resp.success() ? resp->direct_smb_patching_supported() : false);
       });
   protos::gen::InitializeConnectionRequest req;
   req.set_producer_name(name_);
@@ -166,19 +192,21 @@
   PERFETTO_DCHECK_THREAD(thread_checker_);
   PERFETTO_DLOG("Tracing service connection failure");
   connected_ = false;
-  producer_->OnDisconnect();
   data_sources_setup_.clear();
+  producer_->OnDisconnect();  // Note: may delete |this|.
 }
 
 void ProducerIPCClientImpl::OnConnectionInitialized(
     bool connection_succeeded,
-    bool using_shmem_provided_by_producer) {
+    bool using_shmem_provided_by_producer,
+    bool direct_smb_patching_supported) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
   // If connection_succeeded == false, the OnDisconnect() call will follow next
   // and there we'll notify the |producer_|. TODO: add a test for this.
   if (!connection_succeeded)
     return;
   is_shmem_provided_by_producer_ = using_shmem_provided_by_producer;
+  direct_smb_patching_supported_ = direct_smb_patching_supported;
   producer_->OnConnect();
 
   // Bail out if the service failed to adopt our producer-allocated SMB.
@@ -239,6 +267,8 @@
       shared_memory_arbiter_ = SharedMemoryArbiter::CreateInstance(
           shared_memory_.get(), shared_buffer_page_size_kb_ * 1024, this,
           task_runner_);
+      if (direct_smb_patching_supported_)
+        shared_memory_arbiter_->SetDirectSMBPatchingSupportedByService();
     } else {
       // Producer-provided SMB (used by Chrome for startup tracing).
       PERFETTO_CHECK(is_shmem_provided_by_producer_ && shared_memory_ &&
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.h b/src/tracing/ipc/producer/producer_ipc_client_impl.h
index 2995d38..3d66849 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.h
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "perfetto/ext/base/thread_checker.h"
+#include "perfetto/ext/ipc/client.h"
 #include "perfetto/ext/ipc/service_proxy.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/shared_memory.h"
@@ -37,10 +38,6 @@
 class TaskRunner;
 }  // namespace base
 
-namespace ipc {
-class Client;
-}  // namespace ipc
-
 class Producer;
 class SharedMemoryArbiter;
 
@@ -51,16 +48,15 @@
 class ProducerIPCClientImpl : public TracingService::ProducerEndpoint,
                               public ipc::ServiceProxy::EventListener {
  public:
-  ProducerIPCClientImpl(
-      const char* service_sock_name,
-      Producer*,
-      const std::string& producer_name,
-      base::TaskRunner*,
-      TracingService::ProducerSMBScrapingMode,
-      size_t shared_memory_size_hint_bytes = 0,
-      size_t shared_memory_page_size_hint_bytes = 0,
-      std::unique_ptr<SharedMemory> shm = nullptr,
-      std::unique_ptr<SharedMemoryArbiter> shm_arbiter = nullptr);
+  ProducerIPCClientImpl(ipc::Client::ConnArgs,
+                        Producer*,
+                        const std::string& producer_name,
+                        base::TaskRunner*,
+                        TracingService::ProducerSMBScrapingMode,
+                        size_t shared_memory_size_hint_bytes,
+                        size_t shared_memory_page_size_hint_bytes,
+                        std::unique_ptr<SharedMemory> shm,
+                        std::unique_ptr<SharedMemoryArbiter> shm_arbiter);
   ~ProducerIPCClientImpl() override;
 
   // TracingService::ProducerEndpoint implementation.
@@ -91,10 +87,13 @@
   void OnConnect() override;
   void OnDisconnect() override;
 
+  ipc::Client* GetClientForTesting() { return ipc_channel_.get(); }
+
  private:
   // Invoked soon after having established the connection with the service.
   void OnConnectionInitialized(bool connection_succeeded,
-                               bool using_shmem_provided_by_producer);
+                               bool using_shmem_provided_by_producer,
+                               bool direct_smb_patching_supported);
 
   // Invoked when the remote Service sends an IPC to tell us to do something
   // (e.g. start/stop a data source).
@@ -121,6 +120,7 @@
   size_t shared_memory_size_hint_bytes_ = 0;
   TracingService::ProducerSMBScrapingMode const smb_scraping_mode_;
   bool is_shmem_provided_by_producer_ = false;
+  bool direct_smb_patching_supported_ = false;
   std::vector<std::function<void()>> pending_sync_reqs_;
   PERFETTO_THREAD_CHECKER(thread_checker_)
 };
diff --git a/src/tracing/ipc/service/BUILD.gn b/src/tracing/ipc/service/BUILD.gn
index 0bc401c..d8c26d6 100644
--- a/src/tracing/ipc/service/BUILD.gn
+++ b/src/tracing/ipc/service/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_component.gni")
 
 assert(enable_perfetto_ipc)
 
@@ -36,7 +37,11 @@
     "../../../../gn:default_deps",
     "../../../../protos/perfetto/ipc",
     "../../../base",
-    "../../../ipc:host",
     "../../core:service",
   ]
+  if (perfetto_component_type == "static_library") {
+    deps += [ "../../../ipc:perfetto_ipc" ]
+  } else {
+    deps += [ "../../../ipc:host" ]
+  }
 }
diff --git a/src/tracing/ipc/service/consumer_ipc_service.cc b/src/tracing/ipc/service/consumer_ipc_service.cc
index 073bc74..daab5de 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.cc
+++ b/src/tracing/ipc/service/consumer_ipc_service.cc
@@ -308,6 +308,36 @@
   response.Resolve(std::move(resp));
 }
 
+void ConsumerIPCService::SaveTraceForBugreport(
+    const protos::gen::SaveTraceForBugreportRequest&,
+    DeferredSaveTraceForBugreportResponse resp) {
+  RemoteConsumer* remote_consumer = GetConsumerForCurrentRequest();
+  auto it = pending_bugreport_responses_.insert(
+      pending_bugreport_responses_.end(), std::move(resp));
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  auto callback = [weak_this, it](bool success, const std::string& msg) {
+    if (weak_this)
+      weak_this->OnSaveTraceForBugreportCallback(success, msg, std::move(it));
+  };
+  remote_consumer->service_endpoint->SaveTraceForBugreport(callback);
+}
+
+// Called by the service in response to
+// service_endpoint->SaveTraceForBugreport().
+void ConsumerIPCService::OnSaveTraceForBugreportCallback(
+    bool success,
+    const std::string& msg,
+    PendingSaveTraceForBugreportResponses::iterator pending_response_it) {
+  DeferredSaveTraceForBugreportResponse response(
+      std::move(*pending_response_it));
+  pending_bugreport_responses_.erase(pending_response_it);
+  auto resp =
+      ipc::AsyncResult<protos::gen::SaveTraceForBugreportResponse>::Create();
+  resp->set_success(success);
+  resp->set_msg(msg);
+  response.Resolve(std::move(resp));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // RemoteConsumer methods
 ////////////////////////////////////////////////////////////////////////////////
@@ -324,11 +354,14 @@
 // |service_endpoint| (in the RemoteConsumer dtor).
 void ConsumerIPCService::RemoteConsumer::OnDisconnect() {}
 
-void ConsumerIPCService::RemoteConsumer::OnTracingDisabled() {
+void ConsumerIPCService::RemoteConsumer::OnTracingDisabled(
+    const std::string& error) {
   if (enable_tracing_response.IsBound()) {
     auto result =
         ipc::AsyncResult<protos::gen::EnableTracingResponse>::Create();
     result->set_disabled(true);
+    if (!error.empty())
+      result->set_error(error);
     enable_tracing_response.Resolve(std::move(result));
   }
 }
diff --git a/src/tracing/ipc/service/consumer_ipc_service.h b/src/tracing/ipc/service/consumer_ipc_service.h
index d513371..e31f1c7 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.h
+++ b/src/tracing/ipc/service/consumer_ipc_service.h
@@ -69,6 +69,8 @@
                          DeferredQueryServiceStateResponse) override;
   void QueryCapabilities(const protos::gen::QueryCapabilitiesRequest&,
                          DeferredQueryCapabilitiesResponse) override;
+  void SaveTraceForBugreport(const protos::gen::SaveTraceForBugreportRequest&,
+                             DeferredSaveTraceForBugreportResponse) override;
   void OnClientDisconnected() override;
 
  private:
@@ -84,7 +86,7 @@
     // no connection here, these methods are posted straight away.
     void OnConnect() override;
     void OnDisconnect() override;
-    void OnTracingDisabled() override;
+    void OnTracingDisabled(const std::string& error) override;
     void OnTraceData(std::vector<TracePacket>, bool has_more) override;
     void OnDetach(bool) override;
     void OnAttach(bool, const TraceConfig&) override;
@@ -126,6 +128,8 @@
   using PendingQuerySvcResponses = std::list<DeferredQueryServiceStateResponse>;
   using PendingQueryCapabilitiesResponses =
       std::list<DeferredQueryCapabilitiesResponse>;
+  using PendingSaveTraceForBugreportResponses =
+      std::list<DeferredSaveTraceForBugreportResponse>;
 
   ConsumerIPCService(const ConsumerIPCService&) = delete;
   ConsumerIPCService& operator=(const ConsumerIPCService&) = delete;
@@ -140,6 +144,10 @@
                               PendingQuerySvcResponses::iterator);
   void OnQueryCapabilitiesCallback(const TracingServiceCapabilities&,
                                    PendingQueryCapabilitiesResponses::iterator);
+  void OnSaveTraceForBugreportCallback(
+      bool success,
+      const std::string& msg,
+      PendingSaveTraceForBugreportResponses::iterator);
 
   TracingService* const core_service_;
 
@@ -150,6 +158,7 @@
   PendingFlushResponses pending_flush_responses_;
   PendingQuerySvcResponses pending_query_service_responses_;
   PendingQueryCapabilitiesResponses pending_query_capabilities_responses_;
+  PendingSaveTraceForBugreportResponses pending_bugreport_responses_;
 
   base::WeakPtrFactory<ConsumerIPCService> weak_ptr_factory_;  // Keep last.
 };
diff --git a/src/tracing/ipc/service/producer_ipc_service.cc b/src/tracing/ipc/service/producer_ipc_service.cc
index bd79f4f..46a9e3e 100644
--- a/src/tracing/ipc/service/producer_ipc_service.cc
+++ b/src/tracing/ipc/service/producer_ipc_service.cc
@@ -136,6 +136,7 @@
   auto async_res =
       ipc::AsyncResult<protos::gen::InitializeConnectionResponse>::Create();
   async_res->set_using_shmem_provided_by_producer(using_producer_shmem);
+  async_res->set_direct_smb_patching_supported(true);
   response.Resolve(std::move(async_res));
 }
 
diff --git a/src/tracing/platform_posix.cc b/src/tracing/platform_posix.cc
index 75551fc..744b3bd 100644
--- a/src/tracing/platform_posix.cc
+++ b/src/tracing/platform_posix.cc
@@ -93,7 +93,7 @@
   std::string cmdline;
   base::ReadFile("/proc/self/cmdline", &cmdline);
   return cmdline.substr(0, cmdline.find('\0'));
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
   return std::string(getprogname());
 #else
   return "unknown_producer";
diff --git a/src/tracing/test/BUILD.gn b/src/tracing/test/BUILD.gn
index 74ce8e4..442fe3c 100644
--- a/src/tracing/test/BUILD.gn
+++ b/src/tracing/test/BUILD.gn
@@ -103,18 +103,25 @@
       "tracing_module_categories.h",
     ]
   }
-}
 
-# api_test_support needs to be self-contained and not leak any other perfetto
-# deps. See comment in api_test_support.h
-source_set("api_test_support") {
-  testonly = true
-  deps = [
-    "../../../gn:default_deps",
-    "../../base",
-  ]
-  sources = [
-    "api_test_support.cc",
-    "api_test_support.h",
-  ]
+  # api_test_support needs to be self-contained and not leak any other perfetto
+  # deps. See comment in api_test_support.h
+  source_set("api_test_support") {
+    testonly = true
+    deps = [
+      "../../..:libperfetto_client_experimental",
+      "../../../gn:default_deps",
+      "../../base",
+      "../../tracing:client_api_without_backends",
+    ]
+
+    # The system tracing backend requires IPC support.
+    if (enable_perfetto_ipc) {
+      deps += [ "../../../test:test_helper" ]
+    }
+    sources = [
+      "api_test_support.cc",
+      "api_test_support.h",
+    ]
+  }
 }
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index efae7eb..aa9824e 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -21,6 +21,7 @@
 #include <functional>
 #include <list>
 #include <mutex>
+#include <regex>
 #include <thread>
 #include <vector>
 
@@ -46,8 +47,11 @@
 // yyy.gen.h includes are for the test readback path (the code in the test that
 // checks that the results are valid).
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
+#include "protos/perfetto/common/interceptor_descriptor.gen.h"
 #include "protos/perfetto/common/trace_stats.gen.h"
+#include "protos/perfetto/common/tracing_service_state.gen.h"
 #include "protos/perfetto/common/track_event_descriptor.gen.h"
+#include "protos/perfetto/config/interceptor_config.gen.h"
 #include "protos/perfetto/config/track_event/track_event_config.gen.h"
 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.gen.h"
@@ -58,6 +62,7 @@
 #include "protos/perfetto/trace/test_event.gen.h"
 #include "protos/perfetto/trace/test_event.pbzero.h"
 #include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_process_descriptor.gen.h"
@@ -163,6 +168,7 @@
 namespace {
 
 using ::testing::_;
+using ::testing::ContainerEq;
 using ::testing::ElementsAre;
 using ::testing::HasSubstr;
 using ::testing::Invoke;
@@ -175,13 +181,9 @@
 // ------------------------------
 // Declarations of helper classes
 // ------------------------------
-static constexpr auto kWaitEventTimeout = std::chrono::seconds(5);
 
-struct WaitableTestEvent {
-  std::mutex mutex_;
-  std::condition_variable cv_;
-  bool notified_ = false;
-
+class WaitableTestEvent {
+ public:
   bool notified() {
     std::unique_lock<std::mutex> lock(mutex_);
     return notified_;
@@ -189,17 +191,25 @@
 
   void Wait() {
     std::unique_lock<std::mutex> lock(mutex_);
-    if (!cv_.wait_for(lock, kWaitEventTimeout, [this] { return notified_; })) {
-      fprintf(stderr, "Timed out while waiting for event\n");
-      abort();
-    }
+    // TSAN gets confused by wait_for, which we would use here in a perfect
+    // world.
+    cv_.wait(lock, [this] { return notified_; });
   }
 
   void Notify() {
-    std::unique_lock<std::mutex> lock(mutex_);
-    notified_ = true;
+    {
+      std::lock_guard<std::mutex> lock(mutex_);
+      notified_ = true;
+    }
+    // Do not notify while holding the lock, because then we wake up the other
+    // end, only for it to fail to acquire the lock.
     cv_.notify_one();
   }
+
+ private:
+  std::mutex mutex_;
+  std::condition_variable cv_;
+  bool notified_ = false;
 };
 
 class MockDataSource;
@@ -227,8 +237,11 @@
   TestDataSourceHandle* handle_ = nullptr;
 };
 
+constexpr int kTestDataSourceArg = 123;
+
 class MockDataSource2 : public perfetto::DataSource<MockDataSource2> {
  public:
+  MockDataSource2(int arg) { EXPECT_EQ(arg, kTestDataSourceArg); }
   void OnSetup(const SetupArgs&) override {}
   void OnStart(const StartArgs&) override {}
   void OnStop(const StopArgs&) override {}
@@ -257,12 +270,19 @@
   }
 
   std::unique_ptr<perfetto::TraceWriterBase> CreateTraceWriter(
+      perfetto::internal::DataSourceStaticState*,
+      uint32_t,
       perfetto::internal::DataSourceState*,
       perfetto::BufferExhaustedPolicy) override {
     return nullptr;
   }
 
   void DestroyStoppedTraceWritersForCurrentThread() override {}
+  void RegisterInterceptor(
+      const perfetto::InterceptorDescriptor&,
+      InterceptorFactory,
+      perfetto::InterceptorBase::TLSFactory,
+      perfetto::InterceptorBase::TracePacketCallback) override {}
 
   std::vector<DataSource> data_sources;
 
@@ -318,21 +338,44 @@
 // -------------------------
 // Declaration of test class
 // -------------------------
-class PerfettoApiTest : public ::testing::Test {
+class PerfettoApiTest : public ::testing::TestWithParam<perfetto::BackendType> {
  public:
   static PerfettoApiTest* instance;
 
   void SetUp() override {
     instance = this;
 
+    // Start a fresh system service for this test, tearing down any previous
+    // service that was running.
+    uint32_t supported_backends =
+        perfetto::kInProcessBackend | perfetto::kSystemBackend;
+    if (!perfetto::test::StartSystemService())
+      supported_backends &= ~perfetto::kSystemBackend;
+
+    // If the system backend wasn't supported, skip all system backend tests.
+    auto backend = GetParam();
+    if (!(supported_backends & backend))
+      GTEST_SKIP();
+
+    // Since the client API can only be initialized once per process, initialize
+    // both the in-process and system backends for every test here. The actual
+    // service to be used is chosen by the test parameter.
     perfetto::TracingInitArgs args;
-    args.backends = perfetto::kInProcessBackend;
+    args.backends = supported_backends;
     perfetto::Tracing::Initialize(args);
     RegisterDataSource<MockDataSource>("my_data_source");
     perfetto::TrackEvent::Register();
 
     // Make sure our data source always has a valid handle.
     data_sources_["my_data_source"];
+
+    // If this wasn't the first test to run in this process, any producers
+    // connected to the old system service will have been disconnected by the
+    // service restarting above. Wait for all producers to connect again before
+    // proceeding with the test.
+    perfetto::test::SyncProducers();
+
+    perfetto::test::DisableReconnectLimit();
   }
 
   void TearDown() override { instance = nullptr; }
@@ -351,8 +394,7 @@
                                      int fd = -1) {
     sessions_.emplace_back();
     TestTracingSessionHandle* handle = &sessions_.back();
-    handle->session =
-        perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend);
+    handle->session = perfetto::Tracing::NewTrace(/*BackendType=*/GetParam());
     handle->session->SetOnStopCallback([handle] { handle->on_stop.Notify(); });
     handle->session->Setup(cfg, fd);
     return handle;
@@ -579,6 +621,19 @@
     return slices;
   }
 
+  uint32_t GetMainThreadPacketSequenceId(
+      const perfetto::protos::gen::Trace& trace) {
+    for (const auto& packet : trace.packet()) {
+      if (packet.has_track_descriptor() &&
+          packet.track_descriptor().thread().tid() ==
+              static_cast<int32_t>(perfetto::base::GetThreadId())) {
+        return packet.trusted_packet_sequence_id();
+      }
+    }
+    ADD_FAILURE() << "Main thread not found";
+    return 0;
+  }
+
   std::map<std::string, TestDataSourceHandle> data_sources_;
   std::list<TestTracingSessionHandle> sessions_;  // Needs stable pointers.
 };
@@ -620,7 +675,17 @@
 // Test fixtures
 // -------------
 
-TEST_F(PerfettoApiTest, TrackEventStartStopAndDestroy) {
+TEST_P(PerfettoApiTest, StartAndStopWithoutDataSources) {
+  // Create a new trace session without any data sources configured.
+  perfetto::TraceConfig cfg;
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* tracing_session = NewTrace(cfg);
+  // This should not timeout.
+  tracing_session->get()->StartBlocking();
+  tracing_session->get()->StopBlocking();
+}
+
+TEST_P(PerfettoApiTest, TrackEventStartStopAndDestroy) {
   // This test used to cause a use after free as the tracing session got
   // destroyed. It needed to be run approximately 2000 times to catch it so test
   // with --gtest_repeat=3000 (less if running under GDB).
@@ -635,15 +700,14 @@
   // Create five new trace sessions.
   std::vector<std::unique_ptr<perfetto::TracingSession>> sessions;
   for (size_t i = 0; i < 5; ++i) {
-    sessions.push_back(
-        perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend));
+    sessions.push_back(perfetto::Tracing::NewTrace(/*BackendType=*/GetParam()));
     sessions[i]->Setup(cfg);
     sessions[i]->Start();
     sessions[i]->Stop();
   }
 }
 
-TEST_F(PerfettoApiTest, TrackEventStartStopAndStopBlocking) {
+TEST_P(PerfettoApiTest, TrackEventStartStopAndStopBlocking) {
   // This test used to cause a deadlock (due to StopBlocking() after the session
   // already stopped). This usually occurred within 1 or 2 runs of the test so
   // use --gtest_repeat=10
@@ -658,8 +722,7 @@
   // Create five new trace sessions.
   std::vector<std::unique_ptr<perfetto::TracingSession>> sessions;
   for (size_t i = 0; i < 5; ++i) {
-    sessions.push_back(
-        perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend));
+    sessions.push_back(perfetto::Tracing::NewTrace(/*BackendType=*/GetParam()));
     sessions[i]->Setup(cfg);
     sessions[i]->Start();
     sessions[i]->Stop();
@@ -669,6 +732,59 @@
   }
 }
 
+TEST_P(PerfettoApiTest, ChangeTraceConfiguration) {
+  // Setup the trace config.
+  perfetto::TraceConfig trace_config;
+  trace_config.set_duration_ms(2000);
+  trace_config.add_buffers()->set_size_kb(1024);
+  auto* data_source = trace_config.add_data_sources();
+
+  // Configure track events with category "foo".
+  auto* ds_cfg = data_source->mutable_config();
+  ds_cfg->set_name("track_event");
+  perfetto::protos::gen::TrackEventConfig te_cfg;
+  te_cfg.add_disabled_categories("*");
+  te_cfg.add_enabled_categories("foo");
+  ds_cfg->set_track_event_config_raw(te_cfg.SerializeAsString());
+
+  // Initially, exclude all producers (the client library's producer is named
+  // after current process's name, which will not match
+  // "all_producers_excluded").
+  data_source->add_producer_name_filter("all_producers_excluded");
+
+  auto* tracing_session = NewTrace(trace_config);
+
+  tracing_session->get()->StartBlocking();
+
+  // Emit a first trace event, this one should be filtered out due
+  // to the mismatching producer name filter.
+  TRACE_EVENT_BEGIN("foo", "EventFilteredOut");
+  TRACE_EVENT_END("foo");
+
+  // Remove the producer name filter by changing configs.
+  data_source->clear_producer_name_filter();
+  tracing_session->get()->ChangeTraceConfig(trace_config);
+
+  // We don't have a blocking version of ChangeTraceConfig, because there is
+  // currently no response to it from producers or the service. Instead, we sync
+  // the consumer and producer IPC streams for this test, to ensure that the
+  // producer_name_filter change has propagated.
+  tracing_session->get()->GetTraceStatsBlocking();  // sync consumer stream.
+  perfetto::test::SyncProducers();                  // sync producer stream.
+
+  // Emit a second trace event, this one should be included because
+  // the producer name filter was cleared.
+  TRACE_EVENT_BEGIN("foo", "EventIncluded");
+  TRACE_EVENT_END("foo");
+  tracing_session->get()->StopBlocking();
+
+  // Verify that only the second event is in the trace data.
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  std::string trace(raw_trace.data(), raw_trace.size());
+  EXPECT_THAT(trace, Not(HasSubstr("EventFilteredOut")));
+  EXPECT_THAT(trace, HasSubstr("EventIncluded"));
+}
+
 // This is a build-only regression test that checks you can have a track event
 // inside a template.
 template <typename T>
@@ -676,7 +792,7 @@
   TRACE_EVENT_BEGIN("cat", "Name");
 }
 
-TEST_F(PerfettoApiTest, TrackEvent) {
+TEST_P(PerfettoApiTest, TrackEvent) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
   tracing_session->get()->StartBlocking();
@@ -697,7 +813,7 @@
   ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
 
   auto now = perfetto::TrackEvent::GetTraceTimeNs();
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
   auto clock_id = perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME;
 #else
@@ -759,7 +875,7 @@
 
     EXPECT_GT(packet.timestamp(), 0u);
     EXPECT_LE(packet.timestamp(), now);
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) && \
     !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
     EXPECT_FALSE(packet.has_timestamp_clock_id());
 #else
@@ -793,7 +909,7 @@
   TestTrackEventInsideTemplate(true);
 }
 
-TEST_F(PerfettoApiTest, TrackEventCategories) {
+TEST_P(PerfettoApiTest, TrackEventCategories) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
   tracing_session->get()->StartBlocking();
@@ -812,7 +928,7 @@
   EXPECT_THAT(trace, Not(HasSubstr("NotEnabled")));
 }
 
-TEST_F(PerfettoApiTest, TrackEventRegistrationWithModule) {
+TEST_P(PerfettoApiTest, TrackEventRegistrationWithModule) {
   MockTracingMuxer muxer;
 
   // Each track event namespace registers its own data source.
@@ -830,7 +946,7 @@
             muxer.data_sources[1].static_state);
 }
 
-TEST_F(PerfettoApiTest, TrackEventDescriptor) {
+TEST_P(PerfettoApiTest, TrackEventDescriptor) {
   MockTracingMuxer muxer;
 
   perfetto::TrackEvent::Register();
@@ -858,7 +974,7 @@
   EXPECT_EQ("slow", desc.available_categories()[5].tags()[0]);
 }
 
-TEST_F(PerfettoApiTest, TrackEventSharedIncrementalState) {
+TEST_P(PerfettoApiTest, TrackEventSharedIncrementalState) {
   tracing_module::InitializeCategories();
 
   // Setup the trace config.
@@ -885,7 +1001,7 @@
   tracing_session->get()->StopBlocking();
 }
 
-TEST_F(PerfettoApiTest, TrackEventCategoriesWithModule) {
+TEST_P(PerfettoApiTest, TrackEventCategoriesWithModule) {
   // Check that categories defined in two different category registries are
   // enabled and disabled correctly.
   tracing_module::InitializeCategories();
@@ -932,7 +1048,7 @@
   }
 }
 
-TEST_F(PerfettoApiTest, TrackEventDynamicCategories) {
+TEST_P(PerfettoApiTest, TrackEventDynamicCategories) {
   // Setup the trace config.
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
@@ -996,7 +1112,7 @@
   EXPECT_THAT(trace, HasSubstr("EventInSecondStaticallyNamedDynamicCategory"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventConcurrentSessions) {
+TEST_P(PerfettoApiTest, TrackEventConcurrentSessions) {
   // Check that categories that are enabled and disabled in two parallel tracing
   // sessions don't interfere.
 
@@ -1045,7 +1161,7 @@
   EXPECT_THAT(trace2, Not(HasSubstr("Session2_Third")));
 }
 
-TEST_F(PerfettoApiTest, TrackEventProcessAndThreadDescriptors) {
+TEST_P(PerfettoApiTest, TrackEventProcessAndThreadDescriptors) {
   // Thread and process descriptors can be set before tracing is enabled.
   perfetto::TrackEvent::SetProcessDescriptor(
       [](perfetto::protos::pbzero::TrackDescriptor* desc) {
@@ -1104,10 +1220,10 @@
 
   std::vector<perfetto::protos::gen::TrackDescriptor> descs;
   std::vector<perfetto::protos::gen::TrackDescriptor> thread_descs;
-  constexpr uint32_t kMainThreadSequence = 2;
+  uint32_t main_thread_sequence = GetMainThreadPacketSequenceId(trace);
   for (const auto& packet : trace.packet()) {
     if (packet.has_track_descriptor()) {
-      if (packet.trusted_packet_sequence_id() == kMainThreadSequence) {
+      if (packet.trusted_packet_sequence_id() == main_thread_sequence) {
         descs.push_back(packet.track_descriptor());
       } else {
         thread_descs.push_back(packet.track_descriptor());
@@ -1144,7 +1260,7 @@
   EXPECT_NE(0, thread_descs[1].thread().tid());
 }
 
-TEST_F(PerfettoApiTest, CustomTrackDescriptor) {
+TEST_P(PerfettoApiTest, CustomTrackDescriptor) {
   // Setup the trace config.
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
@@ -1159,6 +1275,8 @@
   auto track = perfetto::ProcessTrack::Current();
   auto desc = track.Serialize();
   desc.mutable_process()->set_process_name("testing.exe");
+  desc.mutable_thread()->set_tid(
+      static_cast<int32_t>(perfetto::base::GetThreadId()));
   desc.mutable_chrome_process()->set_process_priority(123);
   perfetto::TrackEvent::SetTrackDescriptor(track, std::move(desc));
   perfetto::TrackEvent::Flush();
@@ -1169,10 +1287,10 @@
   perfetto::protos::gen::Trace trace;
   ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
 
-  constexpr uint32_t kMainThreadSequence = 2;
+  uint32_t main_thread_sequence = GetMainThreadPacketSequenceId(trace);
   bool found_desc = false;
   for (const auto& packet : trace.packet()) {
-    if (packet.trusted_packet_sequence_id() != kMainThreadSequence)
+    if (packet.trusted_packet_sequence_id() != main_thread_sequence)
       continue;
     if (packet.has_track_descriptor()) {
       auto td = packet.track_descriptor();
@@ -1187,7 +1305,7 @@
   EXPECT_TRUE(found_desc);
 }
 
-TEST_F(PerfettoApiTest, TrackEventCustomTrack) {
+TEST_P(PerfettoApiTest, TrackEventCustomTrack) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
   tracing_session->get()->StartBlocking();
@@ -1229,7 +1347,7 @@
 
   // Check that the track uuids match on the begin and end events.
   const auto track = perfetto::Track(async_id);
-  constexpr uint32_t kMainThreadSequence = 2;
+  uint32_t main_thread_sequence = GetMainThreadPacketSequenceId(trace);
   int event_count = 0;
   bool found_descriptor = false;
   for (const auto& packet : trace.packet()) {
@@ -1249,10 +1367,10 @@
     auto track_event = packet.track_event();
     if (track_event.type() ==
         perfetto::protos::gen::TrackEvent::TYPE_SLICE_BEGIN) {
-      EXPECT_EQ(kMainThreadSequence, packet.trusted_packet_sequence_id());
+      EXPECT_EQ(main_thread_sequence, packet.trusted_packet_sequence_id());
       EXPECT_EQ(track.uuid, track_event.track_uuid());
     } else {
-      EXPECT_NE(kMainThreadSequence, packet.trusted_packet_sequence_id());
+      EXPECT_NE(main_thread_sequence, packet.trusted_packet_sequence_id());
       EXPECT_EQ(track.uuid, track_event.track_uuid());
     }
     event_count++;
@@ -1262,7 +1380,7 @@
   perfetto::TrackEvent::EraseTrackDescriptor(track);
 }
 
-TEST_F(PerfettoApiTest, TrackEventCustomTrackAndTimestamp) {
+TEST_P(PerfettoApiTest, TrackEventCustomTrackAndTimestamp) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
   tracing_session->get()->StartBlocking();
@@ -1311,7 +1429,7 @@
   perfetto::TrackEvent::EraseTrackDescriptor(track);
 }
 
-TEST_F(PerfettoApiTest, TrackEventCustomTrackAndTimestampNoLambda) {
+TEST_P(PerfettoApiTest, TrackEventCustomTrackAndTimestampNoLambda) {
   auto* tracing_session = NewTraceWithCategories({"bar"});
   tracing_session->get()->StartBlocking();
 
@@ -1351,7 +1469,7 @@
   EXPECT_EQ(event_count, 2);
 }
 
-TEST_F(PerfettoApiTest, TrackEventAnonymousCustomTrack) {
+TEST_P(PerfettoApiTest, TrackEventAnonymousCustomTrack) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"bar"});
   tracing_session->get()->StartBlocking();
@@ -1385,7 +1503,7 @@
   EXPECT_TRUE(found_descriptor);
 }
 
-TEST_F(PerfettoApiTest, TrackEventTypedArgs) {
+TEST_P(PerfettoApiTest, TrackEventTypedArgs) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1451,7 +1569,7 @@
 
 int InternedLogMessageBody::commit_count = 0;
 
-TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterning) {
+TEST_P(PerfettoApiTest, TrackEventTypedArgsWithInterning) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1518,7 +1636,7 @@
   }
 };
 
-TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningByValue) {
+TEST_P(PerfettoApiTest, TrackEventTypedArgsWithInterningByValue) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1558,7 +1676,7 @@
   }
 };
 
-TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningByHashing) {
+TEST_P(PerfettoApiTest, TrackEventTypedArgsWithInterningByHashing) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1605,7 +1723,7 @@
   }
 };
 
-TEST_F(PerfettoApiTest, TrackEventTypedArgsWithInterningComplexValue) {
+TEST_P(PerfettoApiTest, TrackEventTypedArgsWithInterningComplexValue) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1633,7 +1751,7 @@
               ElementsAre("SomeFunction(file.cc:123): To be, or not to be"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventScoped) {
+TEST_P(PerfettoApiTest, TrackEventScoped) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
   tracing_session->get()->StartBlocking();
@@ -1665,7 +1783,60 @@
                   "E", "B:test.TestEvent", "B:test.AnotherEvent", "E", "E"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventInstant) {
+// A class similar to what Protozero generates for extended message.
+class TestTrackEvent : public perfetto::protos::pbzero::TrackEvent {
+ public:
+  static const int field_number = 9901;
+
+  void set_extension_value(int value) {
+    // 9900-10000 is the range of extension field numbers reserved for testing.
+    AppendTinyVarInt(field_number, value);
+  }
+};
+
+TEST_P(PerfettoApiTest, ExtensionClass) {
+  auto* tracing_session = NewTraceWithCategories({"test"});
+  tracing_session->get()->StartBlocking();
+
+  {
+    TRACE_EVENT("test", "TestEventWithExtensionArgs",
+                [&](perfetto::EventContext ctx) {
+                  ctx.event<TestTrackEvent>()->set_extension_value(42);
+                });
+  }
+
+  perfetto::TrackEvent::Flush();
+  tracing_session->get()->StopBlocking();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  EXPECT_GE(raw_trace.size(), 0u);
+
+  bool found_extension = false;
+  perfetto::protos::pbzero::Trace_Decoder trace(
+      reinterpret_cast<uint8_t*>(raw_trace.data()), raw_trace.size());
+
+  for (auto it = trace.packet(); it; ++it) {
+    perfetto::protos::pbzero::TracePacket_Decoder packet(it->data(),
+                                                         it->size());
+
+    if (!packet.has_track_event())
+      continue;
+
+    auto track_event = packet.track_event();
+    protozero::ProtoDecoder decoder(track_event.data, track_event.size);
+
+    for (protozero::Field f = decoder.ReadField(); f.valid();
+         f = decoder.ReadField()) {
+      if (f.id() == TestTrackEvent::field_number) {
+        found_extension = true;
+      }
+    }
+  }
+
+  EXPECT_TRUE(found_extension);
+}
+
+TEST_P(PerfettoApiTest, TrackEventInstant) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
   tracing_session->get()->StartBlocking();
@@ -1679,7 +1850,7 @@
   EXPECT_THAT(slices, ElementsAre("I:test.TestEvent", "I:test.AnotherEvent"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventDebugAnnotations) {
+TEST_P(PerfettoApiTest, TrackEventDebugAnnotations) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
   tracing_session->get()->StartBlocking();
@@ -1716,7 +1887,7 @@
           "B:test.E(enum_arg=(uint)1)", "B:test.E(signed_enum_arg=(int)-1)"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventCustomDebugAnnotations) {
+TEST_P(PerfettoApiTest, TrackEventCustomDebugAnnotations) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"test"});
   tracing_session->get()->StartBlocking();
@@ -1737,7 +1908,7 @@
           R"(B:test.E(normal_arg=(string)x,custom_arg=(json){"key": 123}))"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventCustomRawDebugAnnotations) {
+TEST_P(PerfettoApiTest, TrackEventCustomRawDebugAnnotations) {
   // Note: this class is also testing a non-moveable and non-copiable argument.
   class MyRawDebugAnnotation : public perfetto::DebugAnnotation {
    public:
@@ -1779,7 +1950,7 @@
                   "B:test.E(plain_arg=(int)42,raw_arg=(nested)nested_value)"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventComputedName) {
+TEST_P(PerfettoApiTest, TrackEventComputedName) {
   // Setup the trace config.
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
@@ -1807,7 +1978,7 @@
                                   "B:test.Even", "B:test.Odd", "B:test.Even"));
 }
 
-TEST_F(PerfettoApiTest, TrackEventArgumentsNotEvaluatedWhenDisabled) {
+TEST_P(PerfettoApiTest, TrackEventArgumentsNotEvaluatedWhenDisabled) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"foo"});
   tracing_session->get()->StartBlocking();
@@ -1829,7 +2000,7 @@
   EXPECT_TRUE(called);
 }
 
-TEST_F(PerfettoApiTest, TrackEventConfig) {
+TEST_P(PerfettoApiTest, TrackEventConfig) {
   auto check_config = [&](perfetto::protos::gen::TrackEventConfig te_cfg) {
     perfetto::TraceConfig cfg;
     cfg.set_duration_ms(500);
@@ -1963,7 +2134,7 @@
   }
 }
 
-TEST_F(PerfettoApiTest, OneDataSourceOneEvent) {
+TEST_P(PerfettoApiTest, OneDataSourceOneEvent) {
   auto* data_source = &data_sources_["my_data_source"];
 
   // Setup the trace config.
@@ -2039,13 +2210,148 @@
   EXPECT_TRUE(test_packet_found);
 }
 
-TEST_F(PerfettoApiTest, BlockingStartAndStop) {
+TEST_P(PerfettoApiTest, ConsumerFlush) {
+  auto* data_source = &data_sources_["my_data_source"];
+
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("my_data_source");
+  ds_cfg->set_legacy_config("test config");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+
+  tracing_session->get()->Start();
+  data_source->on_start.Wait();
+
+  MockDataSource::Trace([&](MockDataSource::TraceContext ctx) {
+    auto packet = ctx.NewTracePacket();
+    packet->set_timestamp(42);
+    packet->set_for_testing()->set_str("flushed event");
+    packet->Finalize();
+
+    // The SMB scraping logic will skip the last packet because it cannot
+    // guarantee it's finalized. Create an empty packet so we get the
+    // previous one and this empty one is ignored.
+    packet = ctx.NewTracePacket();
+  });
+
+  EXPECT_TRUE(tracing_session->get()->FlushBlocking());
+
+  // Deliberately doing ReadTraceBlocking() before StopBlocking() to avoid
+  // hitting the auto scrape-on-stop behavior of the service.
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  tracing_session->get()->StopBlocking();
+
+  ASSERT_GE(raw_trace.size(), 0u);
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  bool test_packet_found = false;
+  for (const auto& packet : trace.packet()) {
+    if (!packet.has_for_testing())
+      continue;
+    EXPECT_FALSE(test_packet_found);
+    EXPECT_EQ(packet.timestamp(), 42U);
+    EXPECT_EQ(packet.for_testing().str(), "flushed event");
+    test_packet_found = true;
+  }
+  EXPECT_TRUE(test_packet_found);
+}
+
+TEST_P(PerfettoApiTest, WithBatching) {
+  auto* data_source = &data_sources_["my_data_source"];
+
+  // Setup the trace config.
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("my_data_source");
+  ds_cfg->set_legacy_config("test config");
+
+  // Create a new trace session.
+  auto* tracing_session = NewTrace(cfg);
+
+  tracing_session->get()->Start();
+  data_source->on_setup.Wait();
+  data_source->on_start.Wait();
+
+  std::stringstream first_large_message;
+  for (size_t i = 0; i < 512; i++)
+    first_large_message << i << ". Something wicked this way comes. ";
+  auto first_large_message_str = first_large_message.str();
+
+  // Emit one trace event before we begin batching.
+  MockDataSource::Trace(
+      [&first_large_message_str](MockDataSource::TraceContext ctx) {
+        auto packet = ctx.NewTracePacket();
+        packet->set_timestamp(42);
+        packet->set_for_testing()->set_str(first_large_message_str);
+        packet->Finalize();
+      });
+
+  // Simulate the start of a batching cycle by first setting the batching period
+  // to a very large value and then force-flushing when we are done writing
+  // data.
+  ASSERT_TRUE(
+      perfetto::test::EnableDirectSMBPatching(/*BackendType=*/GetParam()));
+  perfetto::test::SetBatchCommitsDuration(UINT32_MAX,
+                                          /*BackendType=*/GetParam());
+
+  std::stringstream second_large_message;
+  for (size_t i = 0; i < 512; i++)
+    second_large_message << i << ". Something else wicked this way comes. ";
+  auto second_large_message_str = second_large_message.str();
+
+  // Emit another trace event.
+  MockDataSource::Trace(
+      [&second_large_message_str](MockDataSource::TraceContext ctx) {
+        auto packet = ctx.NewTracePacket();
+        packet->set_timestamp(43);
+        packet->set_for_testing()->set_str(second_large_message_str);
+        packet->Finalize();
+
+        // Simulate the end of the batching cycle.
+        ctx.Flush();
+      });
+
+  data_source->on_stop.Wait();
+  tracing_session->on_stop.Wait();
+
+  std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
+  ASSERT_GE(raw_trace.size(), 0u);
+
+  perfetto::protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), raw_trace.size()));
+  bool test_packet_1_found = false;
+  bool test_packet_2_found = false;
+  for (const auto& packet : trace.packet()) {
+    if (!packet.has_for_testing())
+      continue;
+    EXPECT_TRUE(packet.timestamp() == 42U || packet.timestamp() == 43U);
+    if (packet.timestamp() == 42U) {
+      EXPECT_FALSE(test_packet_1_found);
+      EXPECT_EQ(packet.for_testing().str(), first_large_message_str);
+      test_packet_1_found = true;
+    } else {
+      EXPECT_FALSE(test_packet_2_found);
+      EXPECT_EQ(packet.for_testing().str(), second_large_message_str);
+      test_packet_2_found = true;
+    }
+  }
+  EXPECT_TRUE(test_packet_1_found && test_packet_2_found);
+}
+
+TEST_P(PerfettoApiTest, BlockingStartAndStop) {
   auto* data_source = &data_sources_["my_data_source"];
 
   // Register a second data source to get a bit more coverage.
   perfetto::DataSourceDescriptor dsd;
   dsd.set_name("my_data_source2");
-  MockDataSource2::Register(dsd);
+  MockDataSource2::Register(dsd, kTestDataSourceArg);
+  perfetto::test::SyncProducers();
 
   // Setup the trace config.
   perfetto::TraceConfig cfg;
@@ -2068,7 +2374,7 @@
   EXPECT_TRUE(tracing_session->on_stop.notified());
 }
 
-TEST_F(PerfettoApiTest, BlockingStartAndStopOnEmptySession) {
+TEST_P(PerfettoApiTest, BlockingStartAndStopOnEmptySession) {
   // Setup the trace config.
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
@@ -2083,7 +2389,7 @@
   EXPECT_TRUE(tracing_session->on_stop.notified());
 }
 
-TEST_F(PerfettoApiTest, WriteEventsAfterDeferredStop) {
+TEST_P(PerfettoApiTest, WriteEventsAfterDeferredStop) {
   auto* data_source = &data_sources_["my_data_source"];
   data_source->handle_stop_asynchronously = true;
 
@@ -2148,7 +2454,7 @@
   EXPECT_EQ(test_packet_found, 1);
 }
 
-TEST_F(PerfettoApiTest, RepeatedStartAndStop) {
+TEST_P(PerfettoApiTest, RepeatedStartAndStop) {
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
   cfg.add_buffers()->set_size_kb(1024);
@@ -2166,7 +2472,7 @@
   }
 }
 
-TEST_F(PerfettoApiTest, SetupWithFile) {
+TEST_P(PerfettoApiTest, SetupWithFile) {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   char temp_file[] = "/data/local/tmp/perfetto-XXXXXXXX";
 #else
@@ -2193,11 +2499,12 @@
   EXPECT_EQ(0, unlink(temp_file));
 }
 
-TEST_F(PerfettoApiTest, MultipleRegistrations) {
+TEST_P(PerfettoApiTest, MultipleRegistrations) {
   // Attempt to register the same data source again.
   perfetto::DataSourceDescriptor dsd;
   dsd.set_name("my_data_source");
   EXPECT_TRUE(MockDataSource::Register(dsd));
+  perfetto::test::SyncProducers();
 
   // Setup the trace config.
   perfetto::TraceConfig cfg;
@@ -2221,10 +2528,11 @@
   EXPECT_EQ(trace_lambda_calls, 1);
 }
 
-TEST_F(PerfettoApiTest, CustomIncrementalState) {
+TEST_P(PerfettoApiTest, CustomIncrementalState) {
   perfetto::DataSourceDescriptor dsd;
   dsd.set_name("incr_data_source");
   TestIncrementalDataSource::Register(dsd);
+  perfetto::test::SyncProducers();
 
   // Setup the trace config.
   perfetto::TraceConfig cfg;
@@ -2274,7 +2582,7 @@
 
 // Regression test for b/139110180. Checks that GetDataSourceLocked() can be
 // called from OnStart() and OnStop() callbacks without deadlocking.
-TEST_F(PerfettoApiTest, GetDataSourceLockedFromCallbacks) {
+TEST_P(PerfettoApiTest, GetDataSourceLockedFromCallbacks) {
   auto* data_source = &data_sources_["my_data_source"];
 
   // Setup the trace config.
@@ -2327,7 +2635,7 @@
   EXPECT_EQ(packets_found, 1 | 2 | 4 | 8);
 }
 
-TEST_F(PerfettoApiTest, OnStartCallback) {
+TEST_P(PerfettoApiTest, OnStartCallback) {
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(60000);
   cfg.add_buffers()->set_size_kb(1024);
@@ -2343,7 +2651,35 @@
   tracing_session->get()->StopBlocking();
 }
 
-TEST_F(PerfettoApiTest, GetTraceStats) {
+TEST_P(PerfettoApiTest, OnErrorCallback) {
+  perfetto::TraceConfig cfg;
+
+  // Requesting too long |duration_ms| will cause EnableTracing() to fail.
+  cfg.set_duration_ms(static_cast<uint32_t>(-1));
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  auto* tracing_session = NewTrace(cfg);
+
+  WaitableTestEvent got_error;
+  tracing_session->get()->SetOnErrorCallback([&](perfetto::TracingError error) {
+    EXPECT_EQ(perfetto::TracingError::kTracingFailed, error.code);
+    EXPECT_FALSE(error.message.empty());
+    got_error.Notify();
+  });
+
+  tracing_session->get()->Start();
+  got_error.Wait();
+
+  // Registered error callback will be triggered also by OnDisconnect()
+  // function. This may happen after exiting this test what would result in
+  // system crash (|got_error| will not exist at that time). To prevent that
+  // scenario, error callback has to be cleared.
+  tracing_session->get()->SetOnErrorCallback(nullptr);
+  tracing_session->get()->StopBlocking();
+}
+
+TEST_P(PerfettoApiTest, GetTraceStats) {
   perfetto::TraceConfig cfg;
   cfg.set_duration_ms(500);
   cfg.add_buffers()->set_size_kb(1024);
@@ -2357,6 +2693,7 @@
   tracing_session->get()->GetTraceStats(
       [&got_stats](perfetto::TracingSession::GetTraceStatsCallbackArgs args) {
         perfetto::protos::gen::TraceStats trace_stats;
+        EXPECT_TRUE(args.success);
         EXPECT_TRUE(trace_stats.ParseFromArray(args.trace_stats_data.data(),
                                                args.trace_stats_data.size()));
         EXPECT_EQ(1, trace_stats.buffer_stats_size());
@@ -2367,6 +2704,7 @@
   // Blocking read.
   auto stats = tracing_session->get()->GetTraceStatsBlocking();
   perfetto::protos::gen::TraceStats trace_stats;
+  EXPECT_TRUE(stats.success);
   EXPECT_TRUE(trace_stats.ParseFromArray(stats.trace_stats_data.data(),
                                          stats.trace_stats_data.size()));
   EXPECT_EQ(1, trace_stats.buffer_stats_size());
@@ -2374,7 +2712,50 @@
   tracing_session->get()->StopBlocking();
 }
 
-TEST_F(PerfettoApiTest, LegacyTraceEvents) {
+TEST_P(PerfettoApiTest, QueryServiceState) {
+  class QueryTestDataSource : public perfetto::DataSource<QueryTestDataSource> {
+  };
+  RegisterDataSource<QueryTestDataSource>("query_test_data_source");
+  perfetto::test::SyncProducers();
+
+  auto tracing_session =
+      perfetto::Tracing::NewTrace(/*BackendType=*/GetParam());
+  // Asynchronous read.
+  WaitableTestEvent got_state;
+  tracing_session->QueryServiceState(
+      [&got_state](
+          perfetto::TracingSession::QueryServiceStateCallbackArgs result) {
+        perfetto::protos::gen::TracingServiceState state;
+        EXPECT_TRUE(result.success);
+        EXPECT_TRUE(state.ParseFromArray(result.service_state_data.data(),
+                                         result.service_state_data.size()));
+        EXPECT_EQ(1, state.producers_size());
+        EXPECT_NE(std::string::npos,
+                  state.producers()[0].name().find("integrationtest"));
+        bool found_ds = false;
+        for (const auto& ds : state.data_sources())
+          found_ds |= ds.ds_descriptor().name() == "query_test_data_source";
+        EXPECT_TRUE(found_ds);
+        got_state.Notify();
+      });
+  got_state.Wait();
+
+  // Blocking read.
+  auto result = tracing_session->QueryServiceStateBlocking();
+  perfetto::protos::gen::TracingServiceState state;
+  EXPECT_TRUE(result.success);
+  EXPECT_TRUE(state.ParseFromArray(result.service_state_data.data(),
+                                   result.service_state_data.size()));
+  EXPECT_EQ(1, state.producers_size());
+  EXPECT_NE(std::string::npos,
+            state.producers()[0].name().find("integrationtest"));
+  bool found_ds = false;
+  for (const auto& ds : state.data_sources())
+    found_ds |= ds.ds_descriptor().name() == "query_test_data_source";
+  EXPECT_TRUE(found_ds);
+}
+
+TEST_P(PerfettoApiTest, LegacyTraceEvents) {
   // Create a new trace session.
   auto* tracing_session =
       NewTraceWithCategories({"cat", TRACE_DISABLED_BY_DEFAULT("cat")});
@@ -2428,7 +2809,7 @@
           "Legacy_M:cat.LegacyMetadata"));
 }
 
-TEST_F(PerfettoApiTest, LegacyTraceEventsWithCustomAnnotation) {
+TEST_P(PerfettoApiTest, LegacyTraceEventsWithCustomAnnotation) {
   // Create a new trace session.
   auto* tracing_session = NewTraceWithCategories({"cat"});
   tracing_session->get()->StartBlocking();
@@ -2447,7 +2828,7 @@
                           "B:cat.LegacyEvent(arg=(json){\"key\": 123})"));
 }
 
-TEST_F(PerfettoApiTest, LegacyTraceEventsWithConcurrentSessions) {
+TEST_P(PerfettoApiTest, LegacyTraceEventsWithConcurrentSessions) {
   // Make sure that a uniquely owned debug annotation can be written into
   // multiple concurrent tracing sessions.
 
@@ -2472,7 +2853,7 @@
               ElementsAre("B:cat.LegacyEvent(arg=(json){\"key\": 123})"));
 }
 
-TEST_F(PerfettoApiTest, LegacyTraceEventsWithId) {
+TEST_P(PerfettoApiTest, LegacyTraceEventsWithId) {
   auto* tracing_session = NewTraceWithCategories({"cat"});
   tracing_session->get()->StartBlocking();
 
@@ -2493,7 +2874,7 @@
                                   "string\"):cat.WithScope"));
 }
 
-TEST_F(PerfettoApiTest, LegacyTraceEventsWithFlow) {
+TEST_P(PerfettoApiTest, LegacyTraceEventsWithFlow) {
   auto* tracing_session = NewTraceWithCategories({"cat"});
   tracing_session->get()->StartBlocking();
 
@@ -2529,7 +2910,7 @@
                           "E"));
 }
 
-TEST_F(PerfettoApiTest, LegacyCategoryGroupEnabledState) {
+TEST_P(PerfettoApiTest, LegacyCategoryGroupEnabledState) {
   bool foo_status;
   bool bar_status;
   bool dynamic_status;
@@ -2570,7 +2951,7 @@
   EXPECT_FALSE(*bar_enabled);
 }
 
-TEST_F(PerfettoApiTest, CategoryEnabledState) {
+TEST_P(PerfettoApiTest, CategoryEnabledState) {
   perfetto::DynamicCategory dynamic{"dynamic"};
   EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("foo"));
   EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED("bar"));
@@ -2597,6 +2978,417 @@
   EXPECT_FALSE(TRACE_EVENT_CATEGORY_ENABLED(dynamic));
 }
 
+class TestInterceptor : public perfetto::Interceptor<TestInterceptor> {
+ public:
+  static TestInterceptor* instance;
+
+  struct ThreadLocalState : public perfetto::InterceptorBase::ThreadLocalState {
+    ThreadLocalState(ThreadLocalStateArgs& args) {
+      // Test accessing instance state from the TLS constructor.
+      if (auto self = args.GetInterceptorLocked()) {
+        self->tls_initialized = true;
+      }
+    }
+
+    std::map<uint64_t, std::string> event_names;
+  };
+
+  TestInterceptor(const std::string& constructor_arg) {
+    EXPECT_EQ(constructor_arg, "Constructor argument");
+    // Note: some tests in this suite register multiple track event data
+    // sources. We only track data for the first in this test.
+    if (!instance)
+      instance = this;
+  }
+
+  ~TestInterceptor() override {
+    if (instance != this)
+      return;
+    instance = nullptr;
+    EXPECT_TRUE(setup_called);
+    EXPECT_TRUE(start_called);
+    EXPECT_TRUE(stop_called);
+    EXPECT_TRUE(tls_initialized);
+  }
+
+  void OnSetup(const SetupArgs&) override {
+    EXPECT_FALSE(setup_called);
+    EXPECT_FALSE(start_called);
+    EXPECT_FALSE(stop_called);
+    setup_called = true;
+  }
+
+  void OnStart(const StartArgs&) override {
+    EXPECT_TRUE(setup_called);
+    EXPECT_FALSE(start_called);
+    EXPECT_FALSE(stop_called);
+    start_called = true;
+  }
+
+  void OnStop(const StopArgs&) override {
+    EXPECT_TRUE(setup_called);
+    EXPECT_TRUE(start_called);
+    EXPECT_FALSE(stop_called);
+    stop_called = true;
+  }
+
+  static void OnTracePacket(InterceptorContext context) {
+    perfetto::protos::pbzero::TracePacket::Decoder packet(
+        context.packet_data.data, context.packet_data.size);
+    EXPECT_TRUE(packet.trusted_packet_sequence_id() > 0);
+    {
+      auto self = context.GetInterceptorLocked();
+      ASSERT_TRUE(self);
+      EXPECT_TRUE(self->setup_called);
+      EXPECT_TRUE(self->start_called);
+      EXPECT_FALSE(self->stop_called);
+      EXPECT_TRUE(self->tls_initialized);
+    }
+
+    auto& tls = context.GetThreadLocalState();
+    if (packet.sequence_flags() &
+        perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+      tls.event_names.clear();
+    }
+    if (packet.has_interned_data()) {
+      perfetto::protos::pbzero::InternedData::Decoder interned_data(
+          packet.interned_data());
+      for (auto it = interned_data.event_names(); it; it++) {
+        perfetto::protos::pbzero::EventName::Decoder entry(*it);
+        tls.event_names[entry.iid()] = entry.name().ToStdString();
+      }
+    }
+    if (packet.has_track_event()) {
+      perfetto::protos::pbzero::TrackEvent::Decoder track_event(
+          packet.track_event());
+      uint64_t name_iid = track_event.name_iid();
+      auto self = context.GetInterceptorLocked();
+      self->events.push_back(tls.event_names[name_iid].c_str());
+    }
+  }
+
+  bool setup_called = false;
+  bool start_called = false;
+  bool stop_called = false;
+  bool tls_initialized = false;
+  std::vector<std::string> events;
+};
+
+TestInterceptor* TestInterceptor::instance;
+
+TEST_P(PerfettoApiTest, TracePacketInterception) {
+  perfetto::InterceptorDescriptor desc;
+  desc.set_name("test_interceptor");
+  TestInterceptor::Register(desc, std::string("Constructor argument"));
+
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  ds_cfg->mutable_interceptor_config()->set_name("test_interceptor");
+
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+  EXPECT_EQ(0u, TestInterceptor::instance->events.size());
+
+  // The interceptor should see an event immediately without any explicit
+  // flushing.
+  TRACE_EVENT_BEGIN("foo", "Hip");
+  EXPECT_THAT(TestInterceptor::instance->events, ElementsAre("Hip"));
+
+  // Emit another event with the same title to test interning.
+  TRACE_EVENT_BEGIN("foo", "Hip");
+  EXPECT_THAT(TestInterceptor::instance->events, ElementsAre("Hip", "Hip"));
+
+  // Emit an event from another thread. It should still reach the same
+  // interceptor instance.
+  std::thread thread([] { TRACE_EVENT_BEGIN("foo", "Hooray"); });
+  thread.join();
+  EXPECT_THAT(TestInterceptor::instance->events,
+              ElementsAre("Hip", "Hip", "Hooray"));
+
+  // Emit a packet that spans multiple segments and must be stitched together.
+  TestInterceptor::instance->events.clear();
+  static char long_title[8192];
+  memset(long_title, 'a', sizeof(long_title) - 1);
+  long_title[sizeof(long_title) - 1] = 0;
+  TRACE_EVENT_BEGIN("foo", long_title);
+  EXPECT_THAT(TestInterceptor::instance->events, ElementsAre(long_title));
+
+  tracing_session->get()->StopBlocking();
+}
+
+void EmitConsoleEvents() {
+  TRACE_EVENT_INSTANT("foo", "Instant event");
+  TRACE_EVENT("foo", "Scoped event");
+  TRACE_EVENT_BEGIN("foo", "Nested event");
+  TRACE_EVENT_INSTANT("foo", "Instant event");
+  TRACE_EVENT_INSTANT("foo", "Annotated event", "foo", 1, "bar", "hello");
+  TRACE_EVENT_END("foo");
+  uint64_t async_id = 4004;
+  auto track = perfetto::Track(async_id, perfetto::ThreadTrack::Current());
+  perfetto::TrackEvent::SetTrackDescriptor(
+      track, [](perfetto::protos::pbzero::TrackDescriptor* desc) {
+        desc->set_name("AsyncTrack");
+      });
+  TRACE_EVENT_BEGIN("test", "AsyncEvent", track);
+
+  std::thread thread([&] {
+    TRACE_EVENT("foo", "EventFromAnotherThread");
+    TRACE_EVENT_INSTANT("foo", "Instant event");
+    TRACE_EVENT_END("test", track);
+  });
+  thread.join();
+
+  TRACE_EVENT_INSTANT(
+      "foo", "More annotations", [](perfetto::EventContext ctx) {
+        {
+          auto* dbg = ctx.event()->add_debug_annotations();
+          dbg->set_name("dict");
+          auto* nested = dbg->set_nested_value();
+          nested->set_nested_type(
+              perfetto::protos::pbzero::DebugAnnotation::NestedValue::DICT);
+          nested->add_dict_keys("key");
+          auto* value = nested->add_dict_values();
+          value->set_int_value(123);
+        }
+        {
+          auto* dbg = ctx.event()->add_debug_annotations();
+          dbg->set_name("array");
+          auto* nested = dbg->set_nested_value();
+          nested->set_nested_type(
+              perfetto::protos::pbzero::DebugAnnotation::NestedValue::ARRAY);
+          auto* value = nested->add_array_values();
+          value->set_string_value("first");
+          value = nested->add_array_values();
+          value->set_string_value("second");
+        }
+      });
+}
+
+TEST_P(PerfettoApiTest, ConsoleInterceptorPrint) {
+  perfetto::ConsoleInterceptor::Register();
+
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  ds_cfg->mutable_interceptor_config()->set_name("console");
+
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+  EmitConsoleEvents();
+  tracing_session->get()->StopBlocking();
+}
+
+TEST_P(PerfettoApiTest, ConsoleInterceptorVerify) {
+  perfetto::ConsoleInterceptor::Register();
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  char temp_file[] = "/data/local/tmp/perfetto-XXXXXXXX";
+#else
+  char temp_file[] = "/tmp/perfetto-XXXXXXXX";
+#endif
+  int fd = mkstemp(temp_file);
+  ASSERT_TRUE(fd >= 0);
+  perfetto::ConsoleInterceptor::SetOutputFdForTesting(fd);
+
+  perfetto::TraceConfig cfg;
+  cfg.set_duration_ms(500);
+  cfg.add_buffers()->set_size_kb(1024);
+  auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+  ds_cfg->set_name("track_event");
+  ds_cfg->mutable_interceptor_config()->set_name("console");
+
+  auto* tracing_session = NewTrace(cfg);
+  tracing_session->get()->StartBlocking();
+  EmitConsoleEvents();
+  tracing_session->get()->StopBlocking();
+  perfetto::ConsoleInterceptor::SetOutputFdForTesting(0);
+
+  std::vector<std::string> lines;
+  FILE* f = fdopen(fd, "r");
+  fseek(f, 0u, SEEK_SET);
+  std::array<char, 128> line{};
+  while (fgets(line.data(), line.size(), f)) {
+    // Ignore timestamps and process/thread ids.
+    std::string s(line.data() + 28);
+    // Filter out durations.
+    s = std::regex_replace(s, std::regex(" [+][0-9]*ms"), "");
+    lines.push_back(std::move(s));
+  }
+  fclose(f);
+  EXPECT_EQ(0, unlink(temp_file));
+
+  // clang-format off
+  std::vector<std::string> golden_lines = {
+      "foo   Instant event\n",
+      "foo   Scoped event {\n",
+      "foo   -  Nested event {\n",
+      "foo   -  -  Instant event\n",
+      "foo   -  -  Annotated event(foo:1, bar:hello)\n",
+      "foo   -  } Nested event\n",
+      "test  AsyncEvent {\n",
+      "foo   EventFromAnotherThread {\n",
+      "foo   -  Instant event\n",
+      "test  } AsyncEvent\n",
+      "foo   } EventFromAnotherThread\n",
+      "foo   -  More annotations(dict:{key:123}, array:[first, second])\n",
+      "foo   } Scoped event\n",
+  };
+  // clang-format on
+  EXPECT_THAT(lines, ContainerEq(golden_lines));
+}
+
+TEST_P(PerfettoApiTest, TrackEventObserver) {
+  class Observer : public perfetto::TrackEventSessionObserver {
+   public:
+    ~Observer() override = default;
+
+    void OnSetup(const perfetto::DataSourceBase::SetupArgs&) {
+      // Since other tests here register multiple track event data sources,
+      // ignore all but the first notifications.
+      if (setup_called)
+        return;
+      setup_called = true;
+      if (unsubscribe_at_setup)
+        perfetto::TrackEvent::RemoveSessionObserver(this);
+      // This event isn't recorded in the trace because tracing isn't active yet
+      // when OnSetup is called.
+      TRACE_EVENT_INSTANT("foo", "OnSetup");
+      // However the active tracing categories have already been updated at this
+      // point.
+      EXPECT_TRUE(perfetto::TrackEvent::IsEnabled());
+      EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("foo"));
+    }
+
+    void OnStart(const perfetto::DataSourceBase::StartArgs&) {
+      if (start_called)
+        return;
+      start_called = true;
+      EXPECT_TRUE(perfetto::TrackEvent::IsEnabled());
+      EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("foo"));
+      TRACE_EVENT_INSTANT("foo", "OnStart");
+    }
+
+    void OnStop(const perfetto::DataSourceBase::StopArgs&) {
+      if (stop_called)
+        return;
+      stop_called = true;
+      EXPECT_TRUE(perfetto::TrackEvent::IsEnabled());
+      EXPECT_TRUE(TRACE_EVENT_CATEGORY_ENABLED("foo"));
+      TRACE_EVENT_INSTANT("foo", "OnStop");
+      perfetto::TrackEvent::Flush();
+    }
+
+    bool setup_called{};
+    bool start_called{};
+    bool stop_called{};
+    bool unsubscribe_at_setup{};
+  };
+
+  EXPECT_FALSE(perfetto::TrackEvent::IsEnabled());
+  {
+    Observer observer;
+    perfetto::TrackEvent::AddSessionObserver(&observer);
+
+    auto* tracing_session = NewTraceWithCategories({"foo"});
+    tracing_session->get()->StartBlocking();
+    EXPECT_TRUE(observer.setup_called);
+    EXPECT_TRUE(observer.start_called);
+    tracing_session->get()->StopBlocking();
+    EXPECT_TRUE(observer.stop_called);
+    perfetto::TrackEvent::RemoveSessionObserver(&observer);
+    auto slices = ReadSlicesFromTrace(tracing_session->get());
+    EXPECT_THAT(slices, ElementsAre("I:foo.OnStart", "I:foo.OnStop"));
+  }
+
+  // No notifications after removing observer.
+  {
+    Observer observer;
+    perfetto::TrackEvent::AddSessionObserver(&observer);
+    perfetto::TrackEvent::RemoveSessionObserver(&observer);
+    auto* tracing_session = NewTraceWithCategories({"foo"});
+    tracing_session->get()->StartBlocking();
+    EXPECT_FALSE(observer.setup_called);
+    EXPECT_FALSE(observer.start_called);
+    tracing_session->get()->StopBlocking();
+    EXPECT_FALSE(observer.stop_called);
+  }
+
+  // Removing observer in a callback.
+  {
+    Observer observer;
+    observer.unsubscribe_at_setup = true;
+    perfetto::TrackEvent::AddSessionObserver(&observer);
+    auto* tracing_session = NewTraceWithCategories({"foo"});
+    tracing_session->get()->StartBlocking();
+    EXPECT_TRUE(observer.setup_called);
+    EXPECT_FALSE(observer.start_called);
+    tracing_session->get()->StopBlocking();
+    EXPECT_FALSE(observer.stop_called);
+    perfetto::TrackEvent::RemoveSessionObserver(&observer);
+  }
+
+  // Multiple observers.
+  {
+    Observer observer1;
+    Observer observer2;
+    perfetto::TrackEvent::AddSessionObserver(&observer1);
+    perfetto::TrackEvent::AddSessionObserver(&observer2);
+    auto* tracing_session = NewTraceWithCategories({"foo"});
+    tracing_session->get()->StartBlocking();
+    tracing_session->get()->StopBlocking();
+    perfetto::TrackEvent::RemoveSessionObserver(&observer1);
+    perfetto::TrackEvent::RemoveSessionObserver(&observer2);
+    auto slices = ReadSlicesFromTrace(tracing_session->get());
+    EXPECT_THAT(slices, ElementsAre("I:foo.OnStart", "I:foo.OnStart",
+                                    "I:foo.OnStop", "I:foo.OnStop"));
+  }
+
+  // Multiple observers with one being removed midway.
+  {
+    Observer observer1;
+    Observer observer2;
+    perfetto::TrackEvent::AddSessionObserver(&observer1);
+    perfetto::TrackEvent::AddSessionObserver(&observer2);
+    auto* tracing_session = NewTraceWithCategories({"foo"});
+    tracing_session->get()->StartBlocking();
+    perfetto::TrackEvent::RemoveSessionObserver(&observer1);
+    tracing_session->get()->StopBlocking();
+    perfetto::TrackEvent::RemoveSessionObserver(&observer2);
+    auto slices = ReadSlicesFromTrace(tracing_session->get());
+    EXPECT_THAT(slices,
+                ElementsAre("I:foo.OnStart", "I:foo.OnStart", "I:foo.OnStop"));
+  }
+  EXPECT_FALSE(perfetto::TrackEvent::IsEnabled());
+}
+
+struct BackendTypeAsString {
+  std::string operator()(
+      const ::testing::TestParamInfo<perfetto::BackendType>& info) const {
+    switch (info.param) {
+      case perfetto::kInProcessBackend:
+        return "InProc";
+      case perfetto::kSystemBackend:
+        return "System";
+      case perfetto::kCustomBackend:
+        return "Custom";
+      case perfetto::kUnspecifiedBackend:
+        return "Unspec";
+    }
+    return nullptr;
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(PerfettoApiTest,
+                         PerfettoApiTest,
+                         ::testing::Values(perfetto::kInProcessBackend,
+                                           perfetto::kSystemBackend),
+                         BackendTypeAsString());
+
 }  // namespace
 
 PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource);
diff --git a/src/tracing/test/api_test_support.cc b/src/tracing/test/api_test_support.cc
index 5689a74..f8f5aa8 100644
--- a/src/tracing/test/api_test_support.cc
+++ b/src/tracing/test/api_test_support.cc
@@ -18,13 +18,89 @@
 
 #include "perfetto/base/proc_utils.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/temp_file.h"
+#include "src/tracing/internal/tracing_muxer_impl.h"
+
+#include <sstream>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_IPC)
+#include "test/test_helper.h"
+#endif
 
 namespace perfetto {
 namespace test {
 
+#if PERFETTO_BUILDFLAG(PERFETTO_IPC)
+namespace {
+
+class InProcessSystemService {
+ public:
+  InProcessSystemService() : test_helper_(&task_runner_) {
+    test_helper_.StartService();
+  }
+
+ private:
+  perfetto::base::TestTaskRunner task_runner_;
+  perfetto::TestHelper test_helper_;
+};
+
+}  // namespace
+
+bool StartSystemService() {
+  static InProcessSystemService* system_service;
+
+  // If there already was a system service running, make sure the new one is
+  // running before tearing down the old one. This avoids a 1 second
+  // reconnection delay between each test since the connection to the new
+  // service succeeds immediately.
+  std::unique_ptr<InProcessSystemService> old_service(system_service);
+  system_service = new InProcessSystemService();
+
+  // Tear down the service at process exit to make sure temporary files get
+  // deleted.
+  static bool cleanup_registered;
+  if (!cleanup_registered) {
+    atexit([] { delete system_service; });
+    cleanup_registered = true;
+  }
+  return true;
+}
+#else   // !PERFETTO_BUILDFLAG(PERFETTO_IPC)
+bool StartSystemService() {
+  return false;
+}
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_IPC)
+
 int32_t GetCurrentProcessId() {
   return static_cast<int32_t>(base::GetProcessId());
 }
 
+void SyncProducers() {
+  auto* muxer = reinterpret_cast<perfetto::internal::TracingMuxerImpl*>(
+      perfetto::internal::TracingMuxer::Get());
+  muxer->SyncProducersForTesting();
+}
+
+void SetBatchCommitsDuration(uint32_t batch_commits_duration_ms,
+                             BackendType backend_type) {
+  auto* muxer = reinterpret_cast<perfetto::internal::TracingMuxerImpl*>(
+      perfetto::internal::TracingMuxer::Get());
+  muxer->SetBatchCommitsDurationForTesting(batch_commits_duration_ms,
+                                           backend_type);
+}
+
+void DisableReconnectLimit() {
+  auto* muxer = reinterpret_cast<perfetto::internal::TracingMuxerImpl*>(
+      perfetto::internal::TracingMuxer::Get());
+  muxer->SetMaxProducerReconnectionsForTesting(
+      std::numeric_limits<uint32_t>::max());
+}
+
+bool EnableDirectSMBPatching(BackendType backend_type) {
+  auto* muxer = reinterpret_cast<perfetto::internal::TracingMuxerImpl*>(
+      perfetto::internal::TracingMuxer::Get());
+  return muxer->EnableDirectSMBPatchingForTesting(backend_type);
+}
+
 }  // namespace test
 }  // namespace perfetto
diff --git a/src/tracing/test/api_test_support.h b/src/tracing/test/api_test_support.h
index 9409caf..55ae08f 100644
--- a/src/tracing/test/api_test_support.h
+++ b/src/tracing/test/api_test_support.h
@@ -27,11 +27,21 @@
 //  IMPORTANT: This header must not pull any non-public perfetto header.
 
 #include <stdint.h>
+#include "perfetto/tracing.h"
 
 namespace perfetto {
 namespace test {
 
 int32_t GetCurrentProcessId();
+bool StartSystemService();
+void SyncProducers();
+
+void SetBatchCommitsDuration(uint32_t batch_commits_duration_ms,
+                             BackendType backend_type);
+
+bool EnableDirectSMBPatching(BackendType backend_type);
+
+void DisableReconnectLimit();
 
 }  // namespace test
 }  // namespace perfetto
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index e7cf3b1..2d7dc4c 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -73,7 +73,8 @@
   static int i = 0;
   auto checkpoint_name = "on_tracing_disabled_consumer_" + std::to_string(i++);
   auto on_tracing_disabled = task_runner_->CreateCheckpoint(checkpoint_name);
-  EXPECT_CALL(*this, OnTracingDisabled()).WillOnce(Invoke(on_tracing_disabled));
+  EXPECT_CALL(*this, OnTracingDisabled(_))
+      .WillOnce(testing::InvokeWithoutArgs(on_tracing_disabled));
   task_runner_->RunUntilCheckpoint(checkpoint_name, timeout_ms);
 }
 
diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h
index a1f050e..9ba12b4 100644
--- a/src/tracing/test/mock_consumer.h
+++ b/src/tracing/test/mock_consumer.h
@@ -69,7 +69,7 @@
   // Consumer implementation.
   MOCK_METHOD0(OnConnect, void());
   MOCK_METHOD0(OnDisconnect, void());
-  MOCK_METHOD0(OnTracingDisabled, void());
+  MOCK_METHOD1(OnTracingDisabled, void(const std::string& /*error*/));
   MOCK_METHOD2(OnTraceData,
                void(std::vector<TracePacket>* /*packets*/, bool /*has_more*/));
   MOCK_METHOD1(OnDetach, void(bool));
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 8e4ec3a..b11b7d8 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -45,9 +45,9 @@
 namespace perfetto {
 namespace {
 
+using testing::_;
 using testing::Invoke;
 using testing::InvokeWithoutArgs;
-using testing::_;
 
 constexpr char kProducerSockName[] = TEST_SOCK_NAME("tracing_test-producer");
 constexpr char kConsumerSockName[] = TEST_SOCK_NAME("tracing_test-consumer");
@@ -80,7 +80,7 @@
   // Producer implementation.
   MOCK_METHOD0(OnConnect, void());
   MOCK_METHOD0(OnDisconnect, void());
-  MOCK_METHOD0(OnTracingDisabled, void());
+  MOCK_METHOD1(OnTracingDisabled, void(const std::string& /*error*/));
   MOCK_METHOD2(OnTracePackets, void(std::vector<TracePacket>*, bool));
   MOCK_METHOD1(OnDetach, void(bool));
   MOCK_METHOD2(OnAttach, void(bool, const TraceConfig&));
@@ -297,7 +297,7 @@
           Invoke([&num_pack_rx, all_packets_rx, &trace_config,
                   &saw_clock_snapshot, &saw_trace_config, &saw_trace_stats](
                      std::vector<TracePacket>* packets, bool has_more) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
             const int kExpectedMinNumberOfClocks = 1;
 #else
             const int kExpectedMinNumberOfClocks = 6;
@@ -338,11 +338,41 @@
   auto on_tracing_disabled =
       task_runner_->CreateCheckpoint("on_tracing_disabled");
   EXPECT_CALL(producer_, StopDataSource(_));
-  EXPECT_CALL(consumer_, OnTracingDisabled())
-      .WillOnce(Invoke(on_tracing_disabled));
+  EXPECT_CALL(consumer_, OnTracingDisabled(_))
+      .WillOnce(InvokeWithoutArgs(on_tracing_disabled));
   task_runner_->RunUntilCheckpoint("on_tracing_disabled");
 }
 
+// Regression test for b/172950370.
+TEST_F(TracingIntegrationTest, ValidErrorOnDisconnection) {
+  // Start tracing.
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096 * 10);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("perfetto.test");
+  consumer_endpoint_->EnableTracing(trace_config);
+
+  auto on_create_ds_instance =
+      task_runner_->CreateCheckpoint("on_create_ds_instance");
+  EXPECT_CALL(producer_, OnTracingSetup());
+
+  // Store the arguments passed to SetupDataSource() and later check that they
+  // match the ones passed to StartDataSource().
+  EXPECT_CALL(producer_, SetupDataSource(_, _));
+  EXPECT_CALL(producer_, StartDataSource(_, _))
+      .WillOnce(InvokeWithoutArgs(on_create_ds_instance));
+  task_runner_->RunUntilCheckpoint("on_create_ds_instance");
+
+  EXPECT_CALL(consumer_, OnTracingDisabled(_))
+      .WillOnce(Invoke([](const std::string& err) {
+        EXPECT_THAT(err,
+                    testing::HasSubstr("EnableTracing IPC request rejected"));
+      }));
+
+  // TearDown() will destroy the service via svc_.reset(). That will drop the
+  // connection and trigger the EXPECT_CALL(OnTracingDisabled) above.
+}
+
 TEST_F(TracingIntegrationTest, WriteIntoFile) {
   // Start tracing.
   TraceConfig trace_config;
@@ -390,8 +420,8 @@
   auto on_tracing_disabled =
       task_runner_->CreateCheckpoint("on_tracing_disabled");
   EXPECT_CALL(producer_, StopDataSource(_));
-  EXPECT_CALL(consumer_, OnTracingDisabled())
-      .WillOnce(Invoke(on_tracing_disabled));
+  EXPECT_CALL(consumer_, OnTracingDisabled(_))
+      .WillOnce(InvokeWithoutArgs(on_tracing_disabled));
   task_runner_->RunUntilCheckpoint("on_tracing_disabled");
 
   // Check that |tmp_file| contains a valid trace.proto message.
@@ -517,8 +547,8 @@
   auto on_tracing_disabled =
       task_runner_->CreateCheckpoint("on_tracing_disabled");
   EXPECT_CALL(producer_, StopDataSource(_));
-  EXPECT_CALL(consumer_, OnTracingDisabled())
-      .WillOnce(Invoke(on_tracing_disabled));
+  EXPECT_CALL(consumer_, OnTracingDisabled(_))
+      .WillOnce(InvokeWithoutArgs(on_tracing_disabled));
   task_runner_->RunUntilCheckpoint("on_tracing_disabled");
 }
 
diff --git a/src/tracing/tracing.cc b/src/tracing/tracing.cc
index ded81b0..09c0553 100644
--- a/src/tracing/tracing.cc
+++ b/src/tracing/tracing.cc
@@ -15,12 +15,15 @@
  */
 
 #include "perfetto/tracing/tracing.h"
-#include "perfetto/tracing/internal/track_event_internal.h"
-#include "src/tracing/internal/tracing_muxer_impl.h"
 
+#include <atomic>
 #include <condition_variable>
 #include <mutex>
 
+#include "perfetto/ext/base/waitable_event.h"
+#include "perfetto/tracing/internal/track_event_internal.h"
+#include "src/tracing/internal/tracing_muxer_impl.h"
+
 namespace perfetto {
 
 // static
@@ -28,8 +31,12 @@
   static bool was_initialized = false;
   static TracingInitArgs init_args;
   if (was_initialized) {
-    // Should not be reinitialized with different args.
-    PERFETTO_DCHECK(init_args == args);
+    if (!(init_args == args)) {
+      PERFETTO_ELOG(
+          "Tracing::Initialize() called more than once with different args. "
+          "This is not supported, only the first call will have effect.");
+      PERFETTO_DCHECK(false);
+    }
     return;
   }
 
@@ -47,10 +54,28 @@
       ->CreateTracingSession(backend);
 }
 
+// Can be called from any thread.
+bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
+  std::atomic<bool> flush_result;
+  base::WaitableEvent flush_ack;
+
+  // The non blocking Flush() can be called on any thread. It does the PostTask
+  // internally.
+  Flush(
+      [&flush_ack, &flush_result](bool res) {
+        flush_result = res;
+        flush_ack.Notify();
+      },
+      timeout_ms);
+  flush_ack.Wait();
+  return flush_result;
+}
+
 std::vector<char> TracingSession::ReadTraceBlocking() {
   std::vector<char> raw_trace;
   std::mutex mutex;
   std::condition_variable cv;
+
   bool all_read = false;
 
   ReadTrace([&mutex, &raw_trace, &all_read, &cv](ReadTraceCallbackArgs cb) {
@@ -90,4 +115,26 @@
   return result;
 }
 
+TracingSession::QueryServiceStateCallbackArgs
+TracingSession::QueryServiceStateBlocking() {
+  std::mutex mutex;
+  std::condition_variable cv;
+  QueryServiceStateCallbackArgs result;
+  bool status_read = false;
+
+  QueryServiceState(
+      [&mutex, &result, &status_read, &cv](QueryServiceStateCallbackArgs args) {
+        result = std::move(args);
+        std::unique_lock<std::mutex> lock(mutex);
+        status_read = true;
+        cv.notify_one();
+      });
+
+  {
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [&status_read] { return status_read; });
+  }
+  return result;
+}
+
 }  // namespace perfetto
diff --git a/src/tracing/track_event_state_tracker.cc b/src/tracing/track_event_state_tracker.cc
new file mode 100644
index 0000000..4385f62
--- /dev/null
+++ b/src/tracing/track_event_state_tracker.cc
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/track_event_state_tracker.h"
+
+#include "perfetto/ext/base/hash.h"
+
+#include "protos/perfetto/common/interceptor_descriptor.gen.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
+#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+
+namespace perfetto {
+
+TrackEventStateTracker::~TrackEventStateTracker() = default;
+TrackEventStateTracker::Delegate::~Delegate() = default;
+
+// static
+void TrackEventStateTracker::ProcessTracePacket(
+    Delegate& delegate,
+    SequenceState& sequence_state,
+    const protos::pbzero::TracePacket_Decoder& packet) {
+  UpdateIncrementalState(delegate, sequence_state, packet);
+
+  if (!packet.has_track_event())
+    return;
+  perfetto::protos::pbzero::TrackEvent::Decoder track_event(
+      packet.track_event());
+
+  // TODO(skyostil): Support incremental timestamps.
+  uint64_t timestamp = packet.timestamp();
+
+  Track* track = &sequence_state.track;
+  if (track_event.has_track_uuid()) {
+    auto* session_state = delegate.GetSessionState();
+    if (!session_state)
+      return;  // Tracing must have ended.
+    track = &session_state->tracks[track_event.track_uuid()];
+  }
+
+  // We only log the first category of each event.
+  protozero::ConstChars category{};
+  uint64_t category_iid = 0;
+  if (auto iid_it = track_event.category_iids()) {
+    category_iid = *iid_it;
+    category.data = sequence_state.event_categories[category_iid].data();
+    category.size = sequence_state.event_categories[category_iid].size();
+  } else if (auto cat_it = track_event.categories()) {
+    category.data = reinterpret_cast<const char*>(cat_it->data());
+    category.size = cat_it->size();
+  }
+
+  protozero::ConstChars name{};
+  uint64_t name_iid = track_event.name_iid();
+  uint64_t name_hash = 0;
+  uint64_t duration = 0;
+  if (name_iid) {
+    name.data = sequence_state.event_names[name_iid].data();
+    name.size = sequence_state.event_names[name_iid].size();
+  } else if (track_event.has_name()) {
+    name.data = track_event.name().data;
+    name.size = track_event.name().size;
+  }
+
+  if (name.data) {
+    base::Hash hash;
+    hash.Update(name.data, name.size);
+    name_hash = hash.digest();
+  }
+
+  size_t depth = track->stack.size();
+  switch (track_event.type()) {
+    case protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN: {
+      StackFrame frame;
+      frame.timestamp = timestamp;
+      frame.name_hash = name_hash;
+      if (track_event.has_track_uuid()) {
+        frame.name = name.ToStdString();
+        frame.category = category.ToStdString();
+      } else {
+        frame.name_iid = name_iid;
+        frame.category_iid = category_iid;
+      }
+      track->stack.push_back(std::move(frame));
+      break;
+    }
+    case protos::pbzero::TrackEvent::TYPE_SLICE_END:
+      if (!track->stack.empty()) {
+        const auto& prev_frame = track->stack.back();
+        if (prev_frame.name_iid) {
+          name.data = sequence_state.event_names[prev_frame.name_iid].data();
+          name.size = sequence_state.event_names[prev_frame.name_iid].size();
+        } else {
+          name.data = prev_frame.name.data();
+          name.size = prev_frame.name.size();
+        }
+        name_hash = prev_frame.name_hash;
+        if (prev_frame.category_iid) {
+          category.data =
+              sequence_state.event_categories[prev_frame.category_iid].data();
+          category.size =
+              sequence_state.event_categories[prev_frame.category_iid].size();
+        } else {
+          category.data = prev_frame.category.data();
+          category.size = prev_frame.category.size();
+        }
+        duration = timestamp - prev_frame.timestamp;
+        depth--;
+      }
+      break;
+    case protos::pbzero::TrackEvent::TYPE_INSTANT:
+      break;
+    case protos::pbzero::TrackEvent::TYPE_COUNTER:
+    case protos::pbzero::TrackEvent::TYPE_UNSPECIFIED:
+      // TODO(skyostil): Support counters.
+      return;
+  }
+
+  ParsedTrackEvent parsed_event{track_event};
+  parsed_event.timestamp_ns = timestamp;
+  parsed_event.duration_ns = duration;
+  parsed_event.stack_depth = depth;
+  parsed_event.category = category;
+  parsed_event.name = name;
+  parsed_event.name_hash = name_hash;
+  delegate.OnTrackEvent(*track, parsed_event);
+
+  if (track_event.type() == protos::pbzero::TrackEvent::TYPE_SLICE_END)
+    track->stack.pop_back();
+}
+
+// static
+void TrackEventStateTracker::UpdateIncrementalState(
+    Delegate& delegate,
+    SequenceState& sequence_state,
+    const protos::pbzero::TracePacket_Decoder& packet) {
+#if PERFETTO_DCHECK_IS_ON()
+  if (!sequence_state.sequence_id) {
+    sequence_state.sequence_id = packet.trusted_packet_sequence_id();
+  } else {
+    PERFETTO_DCHECK(sequence_state.sequence_id ==
+                    packet.trusted_packet_sequence_id());
+  }
+#endif
+
+  if (packet.sequence_flags() &
+      perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED) {
+    // Convert any existing event names and categories on the stack to
+    // non-interned strings so we can look up their names even after the
+    // incremental state is gone.
+    for (auto& frame : sequence_state.track.stack) {
+      if (frame.name_iid) {
+        frame.name = sequence_state.event_names[frame.name_iid];
+        frame.name_iid = 0u;
+      }
+      if (frame.category_iid) {
+        frame.category = sequence_state.event_categories[frame.category_iid];
+        frame.category_iid = 0u;
+      }
+    }
+    sequence_state.event_names.clear();
+    sequence_state.event_categories.clear();
+    sequence_state.debug_annotation_names.clear();
+    sequence_state.track.uuid = 0u;
+    sequence_state.track.index = 0u;
+  }
+  if (packet.has_interned_data()) {
+    perfetto::protos::pbzero::InternedData::Decoder interned_data(
+        packet.interned_data());
+    for (auto it = interned_data.event_names(); it; it++) {
+      perfetto::protos::pbzero::EventName::Decoder entry(*it);
+      sequence_state.event_names[entry.iid()] = entry.name().ToStdString();
+    }
+    for (auto it = interned_data.event_categories(); it; it++) {
+      perfetto::protos::pbzero::EventCategory::Decoder entry(*it);
+      sequence_state.event_categories[entry.iid()] = entry.name().ToStdString();
+    }
+    for (auto it = interned_data.debug_annotation_names(); it; it++) {
+      perfetto::protos::pbzero::DebugAnnotationName::Decoder entry(*it);
+      sequence_state.debug_annotation_names[entry.iid()] =
+          entry.name().ToStdString();
+    }
+  }
+  if (packet.has_trace_packet_defaults()) {
+    perfetto::protos::pbzero::TracePacketDefaults::Decoder defaults(
+        packet.trace_packet_defaults());
+    if (defaults.has_track_event_defaults()) {
+      perfetto::protos::pbzero::TrackEventDefaults::Decoder
+          track_event_defaults(defaults.track_event_defaults());
+      sequence_state.track.uuid = track_event_defaults.track_uuid();
+    }
+  }
+  if (packet.has_track_descriptor()) {
+    perfetto::protos::pbzero::TrackDescriptor::Decoder track_descriptor(
+        packet.track_descriptor());
+    auto* session_state = delegate.GetSessionState();
+    auto& track = session_state->tracks[track_descriptor.uuid()];
+    if (!track.index)
+      track.index = static_cast<uint32_t>(session_state->tracks.size() + 1);
+    track.uuid = track_descriptor.uuid();
+
+    track.name = track_descriptor.name().ToStdString();
+    track.pid = 0;
+    track.tid = 0;
+    if (track_descriptor.has_process()) {
+      perfetto::protos::pbzero::ProcessDescriptor::Decoder process(
+          track_descriptor.process());
+      track.pid = process.pid();
+      if (track.name.empty())
+        track.name = process.process_name().ToStdString();
+    } else if (track_descriptor.has_thread()) {
+      perfetto::protos::pbzero::ThreadDescriptor::Decoder thread(
+          track_descriptor.thread());
+      track.pid = thread.pid();
+      track.tid = thread.tid();
+      if (track.name.empty())
+        track.name = thread.thread_name().ToStdString();
+    }
+    delegate.OnTrackUpdated(track);
+
+    // Mirror properties to the default track of the sequence. Note that
+    // this does not catch updates to the default track written through other
+    // sequences.
+    if (track.uuid == sequence_state.track.uuid) {
+      sequence_state.track.index = track.index;
+      sequence_state.track.name = track.name;
+      sequence_state.track.pid = track.pid;
+      sequence_state.track.tid = track.tid;
+      sequence_state.track.user_data = track.user_data;
+    }
+  }
+}
+
+TrackEventStateTracker::ParsedTrackEvent::ParsedTrackEvent(
+    const perfetto::protos::pbzero::TrackEvent::Decoder& track_event_)
+    : track_event(track_event_) {}
+
+}  // namespace perfetto
diff --git a/test/BUILD.gn b/test/BUILD.gn
index c7b7c15..76bb8ce 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -16,59 +16,62 @@
 import("../gn/fuzzer.gni")
 import("../gn/perfetto.gni")
 
-source_set("end_to_end_integrationtests") {
-  testonly = true
-  deps = [
-    ":test_helper",
-    "../gn:default_deps",
-    "../gn:gtest_and_gmock",
-    "../include/perfetto/ext/ipc",
-    "../include/perfetto/ext/traced",
-    "../include/perfetto/protozero",
-    "../protos/perfetto/config:cpp",
-    "../protos/perfetto/config:zero",
-    "../protos/perfetto/config/power:zero",
-    "../protos/perfetto/trace:cpp",
-    "../protos/perfetto/trace:zero",
-    "../protos/perfetto/trace/ftrace:cpp",
-    "../protos/perfetto/trace/power:cpp",
-    "../src/base:base",
-    "../src/base:test_support",
-    "../src/traced/probes/ftrace",
-  ]
+if (!build_with_chromium && enable_perfetto_integration_tests) {
+  source_set("end_to_end_integrationtests") {
+    testonly = true
+    deps = [
+      ":test_helper",
+      "../gn:default_deps",
+      "../gn:gtest_and_gmock",
+      "../include/perfetto/ext/ipc",
+      "../include/perfetto/ext/traced",
+      "../include/perfetto/protozero",
+      "../protos/perfetto/config:cpp",
+      "../protos/perfetto/config:zero",
+      "../protos/perfetto/config/power:zero",
+      "../protos/perfetto/trace:cpp",
+      "../protos/perfetto/trace:zero",
+      "../protos/perfetto/trace/ftrace:cpp",
+      "../protos/perfetto/trace/perfetto:cpp",
+      "../protos/perfetto/trace/power:cpp",
+      "../src/base:base",
+      "../src/base:test_support",
+      "../src/traced/probes/ftrace",
+    ]
 
-  # These binaries are requires by the cmdline tests, which invoke perfetto
-  # and trigger_perfetto via Subprocess.
-  data_deps = [
-    "../src/perfetto_cmd:perfetto",
-    "../src/perfetto_cmd:trigger_perfetto",
-  ]
+    # These binaries are requires by the cmdline tests, which invoke perfetto
+    # and trigger_perfetto via Subprocess.
+    data_deps = [
+      "../src/perfetto_cmd:perfetto",
+      "../src/perfetto_cmd:trigger_perfetto",
+    ]
 
-  sources = [ "end_to_end_integrationtest.cc" ]
-  if (start_daemons_for_testing) {
-    cflags = [ "-DPERFETTO_START_DAEMONS_FOR_TESTING" ]
+    sources = [ "end_to_end_integrationtest.cc" ]
+    if (start_daemons_for_testing) {
+      cflags = [ "-DPERFETTO_START_DAEMONS_FOR_TESTING" ]
 
-    # In CTS mode we use /syste/bin/perfetto for the cmdline tests and the
-    # perfetto_cmd is not required. Outside of CTS mode, instead, we need to
-    # build the cmdline code as part of the test executable.
-    deps += [
-      "../src/perfetto_cmd",
-      "../src/perfetto_cmd:trigger_perfetto_cmd",
+      # In CTS mode we use /syste/bin/perfetto for the cmdline tests and the
+      # perfetto_cmd is not required. Outside of CTS mode, instead, we need to
+      # build the cmdline code as part of the test executable.
+      deps += [
+        "../src/perfetto_cmd",
+        "../src/perfetto_cmd:trigger_perfetto_cmd",
+      ]
+    }
+  }
+
+  executable("client_api_example") {
+    sources = [ "client_api_example.cc" ]
+    deps = [
+      "..:libperfetto_client_experimental",
+      "../gn:default_deps",
+      "../include/perfetto/tracing",
+      "../protos/perfetto/config/gpu:zero",
+      "../protos/perfetto/trace:zero",
+      "../protos/perfetto/trace/gpu:zero",
     ]
   }
-}
-
-executable("client_api_example") {
-  sources = [ "client_api_example.cc" ]
-  deps = [
-    "..:libperfetto_client_experimental",
-    "../gn:default_deps",
-    "../include/perfetto/tracing",
-    "../protos/perfetto/config/gpu:zero",
-    "../protos/perfetto/trace:zero",
-    "../protos/perfetto/trace/gpu:zero",
-  ]
-}
+}  # if (!build_with_chromium && enable_perfetto_integration_tests)
 
 perfetto_fuzzer_test("end_to_end_shared_memory_fuzzer") {
   sources = [ "end_to_end_shared_memory_fuzzer.cc" ]
@@ -96,31 +99,46 @@
   ]
 }
 
-source_set("test_helper") {
-  testonly = true
-  public_deps = [
-    "../protos/perfetto/trace:cpp",
-    "../src/tracing/ipc/consumer",
-    "../src/tracing/ipc/producer",
-    "../src/tracing/ipc/service",
-  ]
-  deps = [
-    "../gn:default_deps",
-    "../include/perfetto/ext/traced",
-    "../protos/perfetto/config:cpp",
-    "../protos/perfetto/trace:zero",
-    "../src/base:test_support",
-    "../src/traced/probes:probes_src",
-    "../src/tracing/ipc:common",
-  ]
-  sources = [
-    "fake_producer.cc",
-    "fake_producer.h",
-    "test_helper.cc",
-    "test_helper.h",
-  ]
-  if (start_daemons_for_testing) {
-    cflags = [ "-DPERFETTO_START_DAEMONS_FOR_TESTING" ]
+# perfetto_fuzzer_test() targets are no-ops if is_fuzzer = false.
+if (enable_perfetto_benchmarks || is_fuzzer ||
+    enable_perfetto_integration_tests) {
+  source_set("test_helper") {
+    testonly = true
+    public_deps = [
+      "../protos/perfetto/trace:cpp",
+      "../src/tracing/ipc/consumer",
+      "../src/tracing/ipc/producer",
+      "../src/tracing/ipc/service",
+    ]
+    deps = [
+      "../gn:default_deps",
+      "../protos/perfetto/config:cpp",
+      "../protos/perfetto/trace:zero",
+      "../src/base:test_support",
+      "../src/ipc:perfetto_ipc",
+      "../src/tracing/ipc:common",
+    ]
+    sources = [
+      "fake_producer.cc",
+      "fake_producer.h",
+      "test_helper.cc",
+      "test_helper.h",
+    ]
+    if (is_android) {
+      sources += [
+        "android_test_utils.cc",
+        "android_test_utils.h",
+      ]
+    }
+    if (start_daemons_for_testing) {
+      cflags = [ "-DPERFETTO_START_DAEMONS_FOR_TESTING" ]
+    }
+    if (!build_with_chromium) {
+      deps += [
+        "../include/perfetto/ext/traced",
+        "../src/traced/probes:probes_src",
+      ]
+    }
   }
 }
 
diff --git a/test/cts/utils.cc b/test/android_test_utils.cc
similarity index 94%
rename from test/cts/utils.cc
rename to test/android_test_utils.cc
index 1dd9b1e..8c6081a 100644
--- a/test/cts/utils.cc
+++ b/test/android_test_utils.cc
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#include "test/cts/utils.h"
+#include "test/android_test_utils.h"
 
 #include <stdlib.h>
 #include <sys/system_properties.h>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace {
@@ -110,7 +109,7 @@
                       uint32_t delay_ms) {
   std::string start_cmd = "am start " + app_name + "/." + activity_name;
   int status = system(start_cmd.c_str());
-  ASSERT_TRUE(status >= 0 && WEXITSTATUS(status) == 0) << "status: " << status;
+  PERFETTO_CHECK(status >= 0 && WEXITSTATUS(status) == 0);
   WaitForProcess(app_name, checkpoint_name, task_runner, delay_ms);
 }
 
@@ -119,7 +118,7 @@
              base::TestTaskRunner* task_runner) {
   std::string stop_cmd = "am force-stop " + app_name;
   int status = system(stop_cmd.c_str());
-  ASSERT_TRUE(status >= 0 && WEXITSTATUS(status) == 0) << "status: " << status;
+  PERFETTO_CHECK(status >= 0 && WEXITSTATUS(status) == 0);
 
   bool desired_run_state = false;
   auto checkpoint = task_runner->CreateCheckpoint(checkpoint_name);
diff --git a/test/cts/utils.h b/test/android_test_utils.h
similarity index 92%
rename from test/cts/utils.h
rename to test/android_test_utils.h
index 724c367..30ee947 100644
--- a/test/cts/utils.h
+++ b/test/android_test_utils.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef TEST_CTS_UTILS_H_
-#define TEST_CTS_UTILS_H_
+#ifndef TEST_ANDROID_TEST_UTILS_H_
+#define TEST_ANDROID_TEST_UTILS_H_
 
 #include <string>
 
@@ -50,4 +50,4 @@
 
 }  // namespace perfetto
 
-#endif  // TEST_CTS_UTILS_H_
+#endif  // TEST_ANDROID_TEST_UTILS_H_
diff --git a/test/ci/linux_tests.sh b/test/ci/linux_tests.sh
index 40d07ed..fd165f2 100755
--- a/test/ci/linux_tests.sh
+++ b/test/ci/linux_tests.sh
@@ -25,7 +25,7 @@
 ${OUT_PATH}/perfetto_integrationtests
 ${OUT_PATH}/trace_processor_minimal_smoke_tests
 
-# If this is a split host+target build, use the trace_processoer_shell binary
+# If this is a split host+target build, use the trace_processor_shell binary
 # from the host directory. In some cases (e.g. lsan x86 builds) the host binary
 # that is copied into the target directory (OUT_PATH) cannot run because depends
 # on libc++.so within the same folder (which is built using target bitness,
@@ -41,6 +41,8 @@
   --perf-file=/ci/artifacts/perf/tp-perf-all.json \
   ${TP_SHELL}
 
+tools/run_python_api_tests.py ${TP_SHELL}
+
 # Don't run benchmarks under x86 (running out of address space because of 4GB)
 # limit or debug (too slow and pointless).
 HOST_CPU="$(tools/gn args --short --list=host_cpu ${OUT_PATH} | awk '{print $3}' | sed -e 's/^"//' -e 's/"$//')"
diff --git a/test/configs/ftrace_with_ksyms.cfg b/test/configs/ftrace_with_ksyms.cfg
new file mode 100644
index 0000000..3a18cfd
--- /dev/null
+++ b/test/configs/ftrace_with_ksyms.cfg
@@ -0,0 +1,36 @@
+duration_ms: 10000
+
+buffers {
+  size_kb: 65536
+}
+
+# Keep first so the early KALLSYMS_PARSE is recorded.
+data_sources {
+  config {
+    name: "perfetto.metatrace"
+    target_buffer: 0
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      symbolize_ksyms: true
+      ftrace_events: "kmem/rss_stat"
+      ftrace_events: "sched/sched_blocked_reason"
+      ftrace_events: "workqueue/workqueue_activate_work"
+      ftrace_events: "workqueue/workqueue_execute_end"
+      ftrace_events: "workqueue/workqueue_execute_start"
+      ftrace_events: "workqueue/workqueue_queue_work"
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+  }
+}
diff --git a/test/configs/long_trace.cfg b/test/configs/long_trace.cfg
index 819c207..448c537 100644
--- a/test/configs/long_trace.cfg
+++ b/test/configs/long_trace.cfg
@@ -10,6 +10,10 @@
 # Writes the userspace buffer into the file every 2.5 seconds.
 file_write_period_ms: 2500
 
+# Commits the trace from the shared memory buffer to the central buffer
+# periodically. Otherwise, the --full-sort option will be required for
+# trace_processor_shell and traceconv when importing the trace.
+flush_period_ms: 20000
 
 # The trace buffers needs to be big enough to hold |file_write_period_ms| of
 # trace data. The trace buffer sizing depends on the number of trace categories
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index 1692c56..e8dd71d 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -6,7 +6,6 @@
     "heapprofd_java_test_cts.cc",
     "heapprofd_test_cts.cc",
     "traced_perf_test_cts.cc",
-    "utils.cc",
     ":perfetto_protos_perfetto_config_cpp_gen",
   ],
   generated_headers: [
diff --git a/test/cts/AndroidTest.xml b/test/cts/AndroidTest.xml
index 3298aa5..800a6d4 100644
--- a/test/cts/AndroidTest.xml
+++ b/test/cts/AndroidTest.xml
@@ -17,7 +17,7 @@
 <configuration description="Config for CTS Perfetto test cases">
     <option name="config-descriptor:metadata" key="component" value="metrics" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="test-suite-tag" value="cts" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/test/cts/BUILD.gn b/test/cts/BUILD.gn
index 84a7860..0404ceb 100644
--- a/test/cts/BUILD.gn
+++ b/test/cts/BUILD.gn
@@ -38,7 +38,6 @@
     "heapprofd_java_test_cts.cc",
     "heapprofd_test_cts.cc",
     "traced_perf_test_cts.cc",
-    "utils.cc",
   ]
 }
 
diff --git a/test/cts/end_to_end_integrationtest_cts.cc b/test/cts/end_to_end_integrationtest_cts.cc
index c1539c7..632db6d 100644
--- a/test/cts/end_to_end_integrationtest_cts.cc
+++ b/test/cts/end_to_end_integrationtest_cts.cc
@@ -20,7 +20,7 @@
 
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
-#include "test/cts/utils.h"
+#include "test/android_test_utils.h"
 #include "test/test_helper.h"
 
 #include "protos/perfetto/config/test_config.gen.h"
diff --git a/test/cts/heapprofd_java_test_cts.cc b/test/cts/heapprofd_java_test_cts.cc
index e8d4701..8888009 100644
--- a/test/cts/heapprofd_java_test_cts.cc
+++ b/test/cts/heapprofd_java_test_cts.cc
@@ -22,7 +22,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
-#include "test/cts/utils.h"
+#include "test/android_test_utils.h"
 #include "test/gtest_and_gmock.h"
 #include "test/test_helper.h"
 
@@ -66,7 +66,7 @@
   helper.WaitForConsumerConnect();
 
   TraceConfig trace_config;
-  trace_config.add_buffers()->set_size_kb(20 * 1024);
+  trace_config.add_buffers()->set_size_kb(40 * 1024);
   trace_config.set_duration_ms(6000);
   trace_config.set_unique_session_name(RandomSessionName().c_str());
 
@@ -80,7 +80,7 @@
 
   // start tracing
   helper.StartTracing(trace_config);
-  helper.WaitForTracingDisabled(10000 /*ms*/);
+  helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
   PERFETTO_CHECK(IsAppRunning(app_name));
@@ -107,7 +107,7 @@
   for (const auto& packet : packets) {
     ASSERT_EQ(packet.heap_graph().roots_size(), 0);
     ASSERT_EQ(packet.heap_graph().objects_size(), 0);
-    ASSERT_EQ(packet.heap_graph().type_names_size(), 0);
+    ASSERT_EQ(packet.heap_graph().types_size(), 0);
     ASSERT_EQ(packet.heap_graph().field_names_size(), 0);
   }
 }
diff --git a/test/cts/heapprofd_test_cts.cc b/test/cts/heapprofd_test_cts.cc
index 05161b5..fc414c2 100644
--- a/test/cts/heapprofd_test_cts.cc
+++ b/test/cts/heapprofd_test_cts.cc
@@ -24,7 +24,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
-#include "test/cts/utils.h"
+#include "test/android_test_utils.h"
 #include "test/gtest_and_gmock.h"
 #include "test/test_helper.h"
 
@@ -95,7 +95,7 @@
 
   // start tracing
   helper.StartTracing(trace_config);
-  helper.WaitForTracingDisabled(10000 /*ms*/);
+  helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
 
@@ -142,7 +142,7 @@
                    /*delay_ms=*/100);
   task_runner.RunUntilCheckpoint("target.app.running", 2000 /*ms*/);
 
-  helper.WaitForTracingDisabled(8000 /*ms*/);
+  helper.WaitForTracingDisabled();
   helper.ReadData();
   helper.WaitForReadData();
 
diff --git a/test/cts/traced_perf_test_cts.cc b/test/cts/traced_perf_test_cts.cc
index bbc0add..df6a6b3 100644
--- a/test/cts/traced_perf_test_cts.cc
+++ b/test/cts/traced_perf_test_cts.cc
@@ -21,7 +21,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/tracing/core/data_source_config.h"
 #include "src/base/test/test_task_runner.h"
-#include "test/cts/utils.h"
+#include "test/android_test_utils.h"
 #include "test/gtest_and_gmock.h"
 #include "test/test_helper.h"
 
diff --git a/test/end_to_end_integrationtest.cc b/test/end_to_end_integrationtest.cc
index f2ac6ef..a8d29e6 100644
--- a/test/end_to_end_integrationtest.cc
+++ b/test/end_to_end_integrationtest.cc
@@ -34,6 +34,7 @@
 #include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/core/commit_data_request.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/tracing/core/tracing_service_state.h"
@@ -50,6 +51,8 @@
 #include "protos/perfetto/trace/ftrace/ftrace.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_stats.gen.h"
+#include "protos/perfetto/trace/perfetto/tracing_service_event.gen.h"
 #include "protos/perfetto/trace/power/battery_counters.gen.h"
 #include "protos/perfetto/trace/test_event.gen.h"
 #include "protos/perfetto/trace/trace.gen.h"
@@ -57,6 +60,10 @@
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "protos/perfetto/trace/trigger.gen.h"
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+#include "test/android_test_utils.h"
+#endif
+
 namespace perfetto {
 
 namespace {
@@ -368,6 +375,80 @@
   ASSERT_EQ(marker_found, 1);
 }
 
+// Disable this test:
+// 1. On cuttlefish (x86-kvm). It's too slow when running on GCE (b/171771440).
+//    We cannot change the length of the production code in
+//    CanReadKernelSymbolAddresses() to deal with it.
+// 2. On user (i.e. non-userdebug) builds. As that doesn't work there by design.
+// 3. On ARM builds, because they fail on our CI.
+#if (PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD) && defined(__i386__)) || \
+    defined(__arm__)
+#define MAYBE_KernelAddressSymbolization DISABLED_KernelAddressSymbolization
+#else
+#define MAYBE_KernelAddressSymbolization KernelAddressSymbolization
+#endif
+TEST_F(PerfettoTest, MAYBE_KernelAddressSymbolization) {
+  // On Android in-tree builds (TreeHugger): this test must always run to
+  // prevent selinux / property-related regressions. However it can run only on
+  // userdebug.
+  // On standalone builds and Linux, this can be optionally skipped because
+  // there it requires root to lower kptr_restrict.
+#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+  if (!IsDebuggableBuild())
+    GTEST_SKIP();
+#else
+  if (geteuid() != 0)
+    GTEST_SKIP();
+#endif
+
+  base::TestTaskRunner task_runner;
+
+  TestHelper helper(&task_runner);
+  helper.StartServiceIfRequired();
+
+#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
+  ProbesProducerThread probes(TEST_PRODUCER_SOCK_NAME);
+  probes.Connect();
+#endif
+
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  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("linux.ftrace");
+  protos::gen::FtraceConfig ftrace_cfg;
+  ftrace_cfg.set_symbolize_ksyms(true);
+  ftrace_cfg.set_initialize_ksyms_synchronously_for_testing(true);
+  ds_config->set_ftrace_config_raw(ftrace_cfg.SerializeAsString());
+
+  helper.StartTracing(trace_config);
+
+  // Synchronize with the ftrace data source. The kernel symbol map is loaded
+  // at this point.
+  helper.FlushAndWait(kDefaultTestTimeoutMs);
+  helper.DisableTracing();
+  helper.WaitForTracingDisabled();
+  helper.ReadData();
+  helper.WaitForReadData();
+
+  const auto& packets = helper.trace();
+  ASSERT_GT(packets.size(), 0u);
+
+  int symbols_parsed = -1;
+  for (const auto& packet : packets) {
+    if (!packet.has_ftrace_stats())
+      continue;
+    if (packet.ftrace_stats().phase() != protos::gen::FtraceStats::END_OF_TRACE)
+      continue;
+    symbols_parsed =
+        static_cast<int>(packet.ftrace_stats().kernel_symbols_parsed());
+  }
+  ASSERT_GT(symbols_parsed, 100);
+}
+
 // TODO(b/73453011): reenable on more platforms (including standalone Android).
 TEST_F(PerfettoTest, TreeHuggerOnly(TestBatteryTracing)) {
   base::TestTaskRunner task_runner;
@@ -511,6 +592,75 @@
   }
 }
 
+// This is a regression test see b/169051440 for context.
+//
+// In this test we ensure that traced will not crash if a Producer stops
+// responding or draining the socket (i.e. after we fill up the IPC buffer
+// traced doesn't block on trying to write to the IPC buffer and watchdog
+// doesn't kill it).
+TEST_F(PerfettoTest, UnresponsiveProducer) {
+  base::TestTaskRunner task_runner;
+
+  TestHelper helper(&task_runner);
+  helper.StartServiceIfRequired();
+  auto* producer = helper.ConnectFakeProducer();
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096 * 10);
+  trace_config.set_duration_ms(100);
+  trace_config.set_flush_timeout_ms(1);
+  trace_config.set_data_source_stop_timeout_ms(1);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.perfetto.FakeProducer");
+
+  static constexpr size_t kNumPackets = 1;
+  static constexpr uint32_t kRandomSeed = 42;
+  static constexpr uint32_t kMsgSize = 1024 * 1024 - 42;
+  ds_config->mutable_for_testing()->set_seed(kRandomSeed);
+  ds_config->mutable_for_testing()->set_message_count(kNumPackets);
+  ds_config->mutable_for_testing()->set_message_size(kMsgSize);
+  ds_config->mutable_for_testing()->set_send_batch_on_register(true);
+
+  // This string is just used to make the StartDataSource IPC larger.
+  ds_config->set_legacy_config(std::string(8192, '.'));
+  ds_config->set_target_buffer(0);
+
+  // Run one legit trace, this ensures that the producer above is
+  // valid and correct and mirrors real life producers.
+  helper.StartTracing(trace_config);
+  helper.WaitForProducerEnabled();
+  helper.WaitForTracingDisabled();
+
+  helper.ReadData();
+  helper.WaitForReadData(/* read_count */ 0, /* timeout_ms */ 10000);
+
+  const auto& packets = helper.trace();
+  ASSERT_EQ(packets.size(), 1u);
+  ASSERT_TRUE(packets[0].has_for_testing());
+  ASSERT_FALSE(packets[0].for_testing().str().empty());
+  helper.FreeBuffers();
+
+  // Switch the producer to ignoring the IPC socket. On a pixel 4 it took 13
+  // traces to fill up the IPC buffer and cause traced to block (and eventually
+  // watchdog to kill it).
+  helper.producer_thread()->get()->RemoveFileDescriptorWatch(
+      producer->unix_socket_fd());
+
+  trace_config.set_duration_ms(1);
+  for (uint32_t i = 0u; i < 15u; i++) {
+    helper.StartTracing(trace_config, base::ScopedFile());
+    helper.WaitForTracingDisabled(/* timeout_ms = */ 20000);
+    helper.FreeBuffers();
+  }
+  // We need to readd the FileDescriptor (otherwise when the UnixSocket attempts
+  // to remove it a the FakeProducer is destroyed will hit a CHECK failure.
+  helper.producer_thread()->get()->AddFileDescriptorWatch(
+      producer->unix_socket_fd(), []() {});
+}
+
 TEST_F(PerfettoTest, DetachAndReattach) {
   base::TestTaskRunner task_runner;
 
@@ -692,6 +842,58 @@
   EXPECT_THAT(ds_found, ElementsAreArray(ds_expected));
 }
 
+TEST_F(PerfettoTest, SaveForBugreport) {
+  base::TestTaskRunner task_runner;
+
+  TestHelper helper(&task_runner);
+  helper.StartServiceIfRequired();
+  helper.ConnectFakeProducer();
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  trace_config.set_duration_ms(10000);
+  trace_config.set_bugreport_score(10);
+  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(3);
+  ds_config->mutable_for_testing()->set_message_size(10);
+  ds_config->mutable_for_testing()->set_send_batch_on_register(true);
+
+  helper.StartTracing(trace_config);
+  helper.WaitForProducerEnabled();
+
+  EXPECT_TRUE(helper.SaveTraceForBugreportAndWait());
+  helper.WaitForTracingDisabled();
+
+  // Read the trace written in the fixed location (/data/misc/perfetto-traces/
+  // on Android, /tmp/ on Linux/Mac) and make sure it has the right contents.
+  std::string trace_str;
+  base::ReadFile(kBugreportTracePath, &trace_str);
+  ASSERT_FALSE(trace_str.empty());
+  protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromString(trace_str));
+  int test_packets = 0;
+  for (const auto& p : trace.packet())
+    test_packets += p.has_for_testing() ? 1 : 0;
+  ASSERT_EQ(test_packets, 3);
+
+  // Now read the trace returned to the consumer via ReadBuffers. This should
+  // be always empty because --save-for-bugreport takes it over and makes the
+  // buffers unreadable by the consumer (by virtue of force-setting
+  // write_into_file, which is incompatible with ReadBuffers()). The only
+  // content should be the |seized_for_bugreport| flag.
+  helper.ReadData();
+  helper.WaitForReadData();
+  const auto& packets = helper.full_trace();
+  ASSERT_EQ(packets.size(), 1u);
+  for (const auto& p : packets) {
+    ASSERT_TRUE(p.has_service_event());
+    ASSERT_TRUE(p.service_event().seized_for_bugreport());
+  }
+}
+
 // Disable cmdline tests on sanitizets because they use fork() and that messes
 // up leak / races detections, which has been fixed only recently (see
 // https://github.com/google/sanitizers/issues/836 ).
@@ -1012,6 +1214,8 @@
   protos::gen::TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(1024);
   trace_config.set_allow_user_build_tracing(true);
+  auto* incident_config = trace_config.mutable_incident_report_config();
+  incident_config->set_destination_package("foo.bar.baz");
   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);
@@ -1055,7 +1259,7 @@
   background_trace.join();
 
   EXPECT_THAT(stderr_str,
-              ::testing::HasSubstr("Skipping write to dropbox. Empty trace."));
+              ::testing::HasSubstr("Skipping write to incident. Empty trace."));
 }
 
 TEST_F(PerfettoCmdlineTest, NoSanitizers(StopTracingTriggerFromConfig)) {
diff --git a/test/fake_producer.cc b/test/fake_producer.cc
index 2fc18a3..d35a674 100644
--- a/test/fake_producer.cc
+++ b/test/fake_producer.cc
@@ -21,12 +21,13 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/core/commit_data_request.h"
 #include "perfetto/ext/tracing/core/shared_memory_arbiter.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "perfetto/tracing/core/data_source_config.h"
+#include "src/ipc/client_impl.h"
+#include "src/tracing/ipc/producer/producer_ipc_client_impl.h"
 
 #include "protos/perfetto/config/test_config.gen.h"
 #include "protos/perfetto/trace/test_event.pbzero.h"
@@ -54,7 +55,7 @@
       socket_name, this, "android.perfetto.FakeProducer", task_runner_,
       TracingService::ProducerSMBScrapingMode::kDefault,
       /*shared_memory_size_hint_bytes=*/0,
-      /*shared_memory_page_size_hint_bytes=*/base::kPageSize, std::move(shm),
+      /*shared_memory_page_size_hint_bytes=*/4096, std::move(shm),
       std::move(shm_arbiter));
   on_connect_ = std::move(on_connect);
   on_setup_data_source_instance_ = std::move(on_setup_data_source_instance);
@@ -160,6 +161,15 @@
   endpoint_->NotifyFlushComplete(flush_request_id);
 }
 
+int FakeProducer::unix_socket_fd() {
+  // Since FakeProducer is only used in tests we can include and assume the
+  // implementation.
+  auto* producer = static_cast<ProducerIPCClientImpl*>(endpoint_.get());
+  auto* ipc_client =
+      static_cast<ipc::ClientImpl*>(producer->GetClientForTesting());
+  return ipc_client->GetUnixSocketForTesting()->fd();
+}
+
 void FakeProducer::SetupFromConfig(const protos::gen::TestConfig& config) {
   rnd_engine_ = std::minstd_rand0(config.seed());
   message_count_ = config.message_count();
diff --git a/test/fake_producer.h b/test/fake_producer.h
index c38e40f..c248051 100644
--- a/test/fake_producer.h
+++ b/test/fake_producer.h
@@ -81,6 +81,9 @@
   void ClearIncrementalState(const DataSourceInstanceID* /*data_source_ids*/,
                              size_t /*num_data_sources*/) override {}
 
+  // For testing, access to the fd used to communicate with the TracingService.
+  int unix_socket_fd();
+
  private:
   void SetupFromConfig(const protos::gen::TestConfig& config);
   void EmitEventBatchOnTaskRunner(std::function<void()> callback);
diff --git a/test/stress_test/BUILD.gn b/test/stress_test/BUILD.gn
new file mode 100644
index 0000000..a5fa275
--- /dev/null
+++ b/test/stress_test/BUILD.gn
@@ -0,0 +1,47 @@
+# 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.
+
+import("../../gn/perfetto.gni")
+
+executable("stress_test") {
+  testonly = true
+  sources = [ "stress_test.cc" ]
+  data_deps = [
+    "../../src/perfetto_cmd:perfetto",
+    "../../src/traced/service:traced",
+  ]
+  deps = [
+    ":stress_producer",
+    "../..:libperfetto_client_experimental",
+    "../../gn:default_deps",
+    "../../include/perfetto/tracing",
+    "../../protos/perfetto/trace:zero",
+    "../../src/base",
+    "../../src/base:test_support",
+    "configs",
+  ]
+}
+
+executable("stress_producer") {
+  testonly = true
+  sources = [ "stress_producer.cc" ]
+  deps = [
+    "../..:libperfetto_client_experimental",
+    "../../gn:default_deps",
+    "../../include/perfetto/tracing",
+    "../../protos/perfetto/trace:zero",
+    "../../src/base",
+    "../../src/base:test_support",
+  ]
+}
diff --git a/test/stress_test/README.md b/test/stress_test/README.md
new file mode 100644
index 0000000..70ee1c2
--- /dev/null
+++ b/test/stress_test/README.md
@@ -0,0 +1,134 @@
+# Perfetto Stress Test
+
+This is a test harness that to stress test the client library (DataSource-level
+only for now).
+
+The test is based on a number of configs in /test/stress_test/configs/*.cfg
+(NOTE: they must be listed in configs/BUILD.gn).
+The config is a /protos/perfetto/config/stress_test_config.proto message, which
+embeds the configuration of the test and a whole trace config.
+
+Each configs defines a testing scenario, determining the general trace config
+and all the settings of the test (e.g., how many producer processes to spawn,
+the write timings).
+
+The test is based on exec()-ing `traced` (the tracing service), `perfetto` (the
+consumer cmdline client) and a variable number of `stress_producer` instances.
+
+`stress_producer` emits events at a configurable rate, writing predictable
+sequences of numbers / string, so that the test harness can easily detect
+corruptions, out-of-order events or gaps.
+
+After running each test, the `stress_test` binary reads back the trace and
+performs a bunch of checks:
+
+- Checks that the number of sequences is exactly equal to #processes x #threads.
+- Checks that each sequence has all the expected packets in the right sequence
+- Checks the payload and correctness of proto nesting of each trace packet.
+- Reports CPU/Memory/Context-switch numbers for the service and producer
+  processes.
+
+Each test config is isolated from the others. All processes are killed and
+re-spawned for each test.
+
+The stdout/err of each process is saved in a dedicated /tmp/ folder, as well as
+the resulting trace.
+
+## Building and running the test
+
+```bash
+# This will recursively build traced, perfetto and stress_producer.
+ninja -C out/default stress_test
+
+out/default/stress_test
+```
+
+will output:
+
+```txt
+[307.909] stress_test.cc:116      Saving test results in /tmp/perfetto-ltIBJgA0
+
+===============================================================
+Config: simple
+===============================================================
+Metric               Expected   Actual
+------               --------   ------
+#Errors              0          0
+Duration [ms]        3000       3109
+Num threads          1          1
+Num packets          1000       1001
+Trace size [KB]      168        170
+Svc RSS [MB]         4          2
+Prod RSS [MB]        ---        1
+Svc CPU [ms]         ---        10
+Prod CPU [ms]        ---        32
+Svc #ctxswitch       ---        103 / 20
+Prod #ctxswitch      ---        1022 / 1
+
+===============================================================
+Config: bursts
+===============================================================
+Metric               Expected   Actual
+------               --------   ------
+#Errors              0          0
+Duration [ms]        2000       2381
+Num threads          10         10
+Num packets          2675       20021
+Trace size [KB]      449        11063
+Svc RSS [MB]         32         17
+Prod RSS [MB]        ---        1
+Svc CPU [ms]         ---        98
+Prod CPU [ms]        ---        17
+Svc #ctxswitch       ---        704 / 1327
+Prod #ctxswitch      ---        421 / 1
+```
+
+```bash
+$ ls -Rlh /tmp/perfetto-ltIBJgA0
+total 0
+drwxr-xr-x  16 primiano  wheel   512B  5 Aug 09:16 bursts
+drwxr-xr-x   9 primiano  wheel   288B  5 Aug 09:16 simple
+drwxr-xr-x  38 primiano  wheel   1.2K  5 Aug 09:16 the_storm
+
+/tmp/perfetto-ltIBJgA0/bursts:
+total 22752
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 errors.log
+-rw-r--r--  1 primiano  wheel   180B  5 Aug 09:16 perfetto.log
+-rw-r--r--  1 primiano  wheel   441B  5 Aug 09:16 producer.0.log
+...
+-rw-r--r--  1 primiano  wheel   441B  5 Aug 09:16 producer.9.log
+-rw-------  1 primiano  wheel    11M  5 Aug 09:16 trace
+-rw-r--r--  1 primiano  wheel   407B  5 Aug 09:16 traced.log
+
+/tmp/perfetto-ltIBJgA0/simple:
+total 400
+srwxr-xr-x  1 primiano  wheel     0B  5 Aug 09:16 consumer.sock
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 errors.log
+-rw-r--r--  1 primiano  wheel   178B  5 Aug 09:16 perfetto.log
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 producer.0.log
+srwxr-xr-x  1 primiano  wheel     0B  5 Aug 09:16 producer.sock
+-rw-------  1 primiano  wheel   167K  5 Aug 09:16 trace
+-rw-r--r--  1 primiano  wheel   406B  5 Aug 09:16 traced.log
+
+/tmp/perfetto-ltIBJgA0/the_storm:
+total 524432
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 errors.log
+-rw-r--r--  1 primiano  wheel   184B  5 Aug 09:16 perfetto.log
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 producer.0.log
+...
+-rw-r--r--  1 primiano  wheel     0B  5 Aug 09:16 producer.127.log
+-rw-------  1 primiano  wheel   248M  5 Aug 09:16 trace
+-rw-r--r--  1 primiano  wheel   408B  5 Aug 09:16 traced.log
+```
+
+## TODOs
+
+The following scenarios requires more coverage:
+
+- Nested messages.
+- Force losses and check that the last_dropped flag is consistent.
+- Flushes and scraping.
+- Report data losses in the test output.
+- Multibuffer scenarios.
+- write_into_file=true.
+- Vary page size, smb size.
diff --git a/test/stress_test/configs/BUILD.gn b/test/stress_test/configs/BUILD.gn
new file mode 100644
index 0000000..6a12a09
--- /dev/null
+++ b/test/stress_test/configs/BUILD.gn
@@ -0,0 +1,50 @@
+# 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.
+
+import("../../../gn/perfetto.gni")
+
+config("include_path") {
+  include_dirs = [ target_gen_dir ]
+}
+
+action("configs") {
+  testonly = true
+
+  sources = [
+    "backfills.cfg",
+    "bursts.cfg",
+    "heavy.cfg",
+    "simple.cfg",
+    "stalls.cfg",
+    "xxl_packets.cfg",
+  ]
+
+  protoc_target = "../../../gn:protoc($host_toolchain)"
+  protoc_out_dir = get_label_info(protoc_target, "root_out_dir")
+  protoc_rel_dir = rebase_path(protoc_out_dir, root_build_dir)
+  out_header = "$target_gen_dir/stress_test_config_blobs.h"
+  out_header_rel = rebase_path(out_header, root_build_dir)
+
+  deps = [ protoc_target ]
+  script = "../gen_configs_blob.py"
+  outputs = [ out_header ]
+  args = [
+    "--protoc=$protoc_rel_dir/protoc",
+    "--out=$out_header_rel",
+  ]
+  foreach(source, sources) {
+    args += [ rebase_path(source, root_build_dir) ]
+  }
+  public_configs = [ ":include_path" ]
+}
diff --git a/test/stress_test/configs/backfills.cfg b/test/stress_test/configs/backfills.cfg
new file mode 100644
index 0000000..24e1b17
--- /dev/null
+++ b/test/stress_test/configs/backfills.cfg
@@ -0,0 +1,24 @@
+# TODO(primiano): this fails with the errors below, investigate.
+# FAIL: TestEvent counter mismatch for sequence 2. Expected 100 got 99
+# FAIL: TestEvent counter mismatch for sequence 3. Expected 99 got 98
+# FAIL: TestEvent counter mismatch for sequence 4. Expected 106 got 105
+# FAIL: TestEvent counter mismatch for sequence 5. Expected 107 got 106
+# FAIL: TestEvent counter mismatch for sequence 6. Expected 102 got 101
+# FAIL: TestEvent counter mismatch for sequence 7. Expected 104 got 103
+# FAIL: TestEvent counter mismatch for sequence 8. Expected 111 got 110
+# FAIL: TestEvent counter mismatch for sequence 9. Expected 109 got 108
+
+num_processes: 1
+num_threads: 8
+
+steady_state_timings {
+  rate_mean: 100
+  payload_mean: 640
+  payload_write_time_ms: 100
+}
+
+trace_config {
+  duration_ms: 10000
+  buffers { size_kb: 500000 }
+  data_sources { config { name: "perfetto.stress_test" } }
+}
diff --git a/test/stress_test/configs/bursts.cfg b/test/stress_test/configs/bursts.cfg
new file mode 100644
index 0000000..c8a0fdf
--- /dev/null
+++ b/test/stress_test/configs/bursts.cfg
@@ -0,0 +1,22 @@
+num_processes: 10
+num_threads: 1
+
+steady_state_timings {
+  rate_mean: 10
+  payload_mean: 128
+}
+
+# 250ms every 2s enter burst mode, bumping at 1000 events/s * 512 ~= 5 MB/s
+# (per thread) ~= 50 MB/s for the 10 processes.
+burst_period_ms: 2000
+burst_duration_ms: 250
+burst_timings {
+  rate_mean: 1000
+  payload_mean: 512
+}
+
+trace_config {
+  duration_ms: 2000
+  buffers { size_kb: 20000 }
+  data_sources { config { name: "perfetto.stress_test" } }
+}
diff --git a/test/stress_test/configs/heavy.cfg b/test/stress_test/configs/heavy.cfg
new file mode 100644
index 0000000..c137d5c
--- /dev/null
+++ b/test/stress_test/configs/heavy.cfg
@@ -0,0 +1,27 @@
+num_processes: 32
+num_threads: 10
+nesting: 10
+
+steady_state_timings {
+  rate_mean: 10
+  payload_mean: 128
+}
+
+burst_period_ms: 2000
+burst_duration_ms: 250
+burst_timings {
+  rate_mean: 1000
+  payload_mean: 128
+}
+
+trace_config {
+  duration_ms: 5000
+  buffers { size_kb: 320000 }
+  data_sources { config { name: "perfetto.stress_test" } }
+
+  producers {
+    producer_name: "stress_producer"
+    shm_size_kb: 4000
+    page_size_kb: 4
+  }
+}
diff --git a/test/stress_test/configs/simple.cfg b/test/stress_test/configs/simple.cfg
new file mode 100644
index 0000000..4632574
--- /dev/null
+++ b/test/stress_test/configs/simple.cfg
@@ -0,0 +1,16 @@
+num_processes: 1
+num_threads: 1
+max_events: 1000
+nesting: 8
+
+# 500 events/s, ~128 bytes/event ~= 64 KB/s
+steady_state_timings {
+  rate_mean: 500
+  payload_mean: 128
+}
+
+trace_config {
+  duration_ms: 3000
+  buffers { size_kb: 8000 }
+  data_sources { config { name: "perfetto.stress_test" } }
+}
diff --git a/test/stress_test/configs/stalls.cfg b/test/stress_test/configs/stalls.cfg
new file mode 100644
index 0000000..344194f
--- /dev/null
+++ b/test/stress_test/configs/stalls.cfg
@@ -0,0 +1,15 @@
+num_processes: 8
+num_threads: 4
+max_events: 10000
+
+# 1K events/s * 10K = 10 MB/s per thread
+steady_state_timings {
+  rate_mean: 1000
+  payload_mean: 10000
+}
+
+trace_config {
+  duration_ms: 5000
+  buffers { size_kb: 500000 }
+  data_sources { config { name: "perfetto.stress_test" } }
+}
diff --git a/test/stress_test/configs/xxl_packets.cfg b/test/stress_test/configs/xxl_packets.cfg
new file mode 100644
index 0000000..fbafb2a
--- /dev/null
+++ b/test/stress_test/configs/xxl_packets.cfg
@@ -0,0 +1,22 @@
+# Four threads writing large and nested packets of 32MB each every second.
+
+num_processes: 1
+num_threads: 4
+max_events: 5
+nesting: 2
+
+# Each writer will write packets of 16 MB ((1 + nesting=1) x payload 8MB)
+steady_state_timings {
+  rate_mean: 1
+  payload_mean: 8000000
+  payload_write_time_ms: 100
+}
+
+trace_config {
+  duration_ms: 10000
+  buffers {
+    size_kb: 500000
+    fill_policy: DISCARD
+  }
+  data_sources { config { name: "perfetto.stress_test" } }
+}
diff --git a/test/stress_test/gen_configs_blob.py b/test/stress_test/gen_configs_blob.py
new file mode 100644
index 0000000..ba1f2c7
--- /dev/null
+++ b/test/stress_test/gen_configs_blob.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+# 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.
+""" Compiles the stress_test configs protos and bundles in a .h C++ array.
+
+This scripts takes all the configs in /test/stress_test/configs, compiles them
+with protoc and generates a C++ header which contains the configs' names and
+proto-encoded bytes.
+
+This is invoked by the build system and is used by the stress_test runner. The
+goal is making the stress_test binary hermetic and not depend on the repo.
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+import os
+import sys
+import argparse
+import shutil
+import subprocess
+
+CUR_DIR = os.path.dirname(os.path.realpath(__file__))
+ROOT_DIR = os.path.dirname(os.path.dirname(CUR_DIR))
+CONFIGS_DIR = os.path.join(CUR_DIR, 'configs')
+
+
+def find_protoc():
+  for root, _, files in os.walk(os.path.join(ROOT_DIR, 'out')):
+    if 'protoc' in files:
+      return os.path.join(root, 'protoc')
+  return None
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--protoc')
+  parser.add_argument('--out', required=True)
+  parser.add_argument('cfgfiles', nargs='+')
+  args = parser.parse_args()
+
+  protoc = args.protoc or find_protoc()
+  assert protoc, 'protoc not found, pass --protoc /path/to/protoc'
+  assert os.path.exists(protoc), '{} does not exist'.format(protoc)
+  if protoc is not args.protoc:
+    print('Using protoc: {}'.format(protoc))
+
+  blobs = {}
+  for cfg_path in args.cfgfiles:
+    cfg_name = os.path.splitext(cfg_path)[0].split(os.sep)[-1]
+    with open(cfg_path, 'r') as in_file:
+      compiled_proto = subprocess.check_output([
+          protoc,
+          '--encode=perfetto.protos.StressTestConfig',
+          '--proto_path=' + ROOT_DIR,
+          os.path.join(ROOT_DIR, 'protos', 'perfetto', 'config',
+                       'stress_test_config.proto'),
+      ],
+                                               stdin=in_file)
+    blobs[cfg_name] = bytearray(compiled_proto)
+
+  # Write the C++ header file
+  fout = open(args.out, 'wb')
+  include_guard = args.out.replace('/', '_').replace('.', '_').upper() + '_'
+  fout.write("""
+#ifndef {include_guard}
+#define {include_guard}
+
+#include <stddef.h>
+#include <stdint.h>
+
+// This file was autogenerated by ${gen_script}. Do not edit.
+
+namespace perfetto {{
+namespace {{
+
+struct StressTestConfigBlob {{
+  const char* name;
+  const uint8_t* data;
+  size_t size;
+}};\n\n""".format(
+      gen_script=__file__,
+      include_guard=include_guard,
+  ).encode())
+
+  configs_arr = '\nconst StressTestConfigBlob kStressTestConfigs[] = {\n'
+  for cfg_name, blob in blobs.items():
+    arr_str = ','.join(str(b) for b in blob)
+    line = 'const uint8_t _config_%s[]{%s};\n' % (cfg_name, arr_str)
+    fout.write(line.encode())
+    configs_arr += '  {{"{n}", _config_{n}, sizeof(_config_{n})}},\n'.format(
+        n=cfg_name)
+  configs_arr += '};\n'
+  fout.write(configs_arr.encode())
+  fout.write("""
+}  // namespace
+}  // namespace perfetto
+#endif\n""".encode())
+  fout.close()
+
+
+if __name__ == '__main__':
+  exit(main())
diff --git a/test/stress_test/stress_producer.cc b/test/stress_test/stress_producer.cc
new file mode 100644
index 0000000..2845efc
--- /dev/null
+++ b/test/stress_test/stress_producer.cc
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <list>
+#include <random>
+#include <thread>
+
+#include "perfetto/base/time.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/tracing.h"
+
+#include "protos/perfetto/config/stress_test_config.gen.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
+
+using StressTestConfig = perfetto::protos::gen::StressTestConfig;
+
+namespace perfetto {
+namespace {
+
+StressTestConfig* g_cfg;
+
+class StressTestDataSource : public DataSource<StressTestDataSource> {
+ public:
+  constexpr static BufferExhaustedPolicy kBufferExhaustedPolicy =
+      BufferExhaustedPolicy::kStall;
+
+  void OnSetup(const SetupArgs& args) override;
+  void OnStart(const StartArgs&) override;
+  void OnStop(const StopArgs&) override;
+
+ private:
+  class Worker {
+   public:
+    explicit Worker(uint32_t id) : id_(id) {}
+    void Start();
+    void Stop();
+    ~Worker() { Stop(); }
+
+   private:
+    void WorkerMain(uint32_t worker_id);
+    void FillPayload(const StressTestConfig::WriterTiming&,
+                     uint32_t seq,
+                     uint32_t nesting,
+                     protos::pbzero::TestEvent::TestPayload*);
+
+    const uint32_t id_;
+    std::thread thread_;
+    std::atomic<bool> quit_;
+    std::minstd_rand0 rnd_seq_;
+
+    // Use a different engine for the generation of random value, keep rnd_seq_
+    // dedicated to generating deterministic sequences.
+    std::minstd_rand0 rnd_gen_;
+  };
+
+  std::list<Worker> workers_;
+};
+
+// Called before the tracing session starts.
+void StressTestDataSource::OnSetup(const SetupArgs&) {
+  for (uint32_t i = 0; i < std::max(g_cfg->num_threads(), 1u); ++i)
+    workers_.emplace_back(i);
+}
+
+// Called when the tracing session starts.
+void StressTestDataSource::OnStart(const StartArgs&) {
+  for (auto& worker : workers_)
+    worker.Start();
+}
+
+// Called when the tracing session ends.
+void StressTestDataSource::OnStop(const StopArgs&) {
+  for (auto& worker : workers_)
+    worker.Stop();
+  workers_.clear();
+}
+
+void StressTestDataSource::Worker::Start() {
+  quit_.store(false);
+  thread_ = std::thread(&StressTestDataSource::Worker::WorkerMain, this, id_);
+}
+
+void StressTestDataSource::Worker::Stop() {
+  if (!thread_.joinable() || quit_)
+    return;
+  PERFETTO_DLOG("Stopping worker %u", id_);
+  quit_.store(true);
+  thread_.join();
+}
+
+void StressTestDataSource::Worker::WorkerMain(uint32_t worker_id) {
+  PERFETTO_DLOG("Worker %u starting", worker_id);
+  rnd_seq_ = std::minstd_rand0(0);
+  int64_t t_start = base::GetBootTimeNs().count();
+  int64_t num_msgs = 0;
+
+  const int64_t max_msgs = g_cfg->max_events()
+                               ? static_cast<int64_t>(g_cfg->max_events())
+                               : INT64_MAX;
+  bool is_last = false;
+  while (!is_last) {
+    is_last = quit_ || ++num_msgs >= max_msgs;
+
+    const int64_t now = base::GetBootTimeNs().count();
+    const auto elapsed_ms = static_cast<uint64_t>((now - t_start) / 1000000);
+
+    const auto* timings = &g_cfg->steady_state_timings();
+    if (g_cfg->burst_period_ms() &&
+        elapsed_ms % g_cfg->burst_period_ms() >
+            (g_cfg->burst_period_ms() - g_cfg->burst_duration_ms())) {
+      timings = &g_cfg->burst_timings();
+    }
+    std::normal_distribution<> rate_dist{timings->rate_mean(),
+                                         timings->rate_stddev()};
+
+    double period_ns = 1e9 / rate_dist(rnd_gen_);
+    period_ns = isnan(period_ns) || period_ns == 0.0 ? 1 : period_ns;
+    double expected_msgs = static_cast<double>(now - t_start) / period_ns;
+    int64_t delay_ns = 0;
+    if (static_cast<int64_t>(expected_msgs) < num_msgs)
+      delay_ns = static_cast<int64_t>(period_ns);
+    std::this_thread::sleep_for(
+        std::chrono::nanoseconds(static_cast<int64_t>(delay_ns)));
+
+    StressTestDataSource::Trace([&](StressTestDataSource::TraceContext ctx) {
+      const uint32_t seq = static_cast<uint32_t>(rnd_seq_());
+      auto packet = ctx.NewTracePacket();
+      packet->set_timestamp(static_cast<uint64_t>(now));
+      auto* test_event = packet->set_for_testing();
+      test_event->set_seq_value(seq);
+      test_event->set_counter(static_cast<uint64_t>(num_msgs));
+      if (is_last)
+        test_event->set_is_last(true);
+
+      FillPayload(*timings, seq, g_cfg->nesting(), test_event->set_payload());
+    });  // Trace().
+
+  }  // while (!quit)
+  PERFETTO_DLOG("Worker done");
+}
+
+void StressTestDataSource::Worker::FillPayload(
+    const StressTestConfig::WriterTiming& timings,
+    uint32_t seq,
+    uint32_t nesting,
+    protos::pbzero::TestEvent::TestPayload* payload) {
+  // Write the payload in two halves, optionally with some delay in the
+  // middle.
+  std::normal_distribution<> msg_size_dist{timings.payload_mean(),
+                                           timings.payload_stddev()};
+  auto payload_size =
+      static_cast<uint32_t>(std::max(std::round(msg_size_dist(rnd_gen_)), 0.0));
+  std::string buf;
+  buf.resize(payload_size / 2);
+  for (size_t i = 0; i < buf.size(); ++i) {
+    buf[i] = static_cast<char>(33 + ((seq + i) % 64));  // Stay ASCII.
+  }
+  payload->add_str(buf);
+  payload->set_remaining_nesting_depth(nesting);
+  if (timings.payload_write_time_ms() > 0) {
+    std::this_thread::sleep_for(
+        std::chrono::milliseconds(timings.payload_write_time_ms()));
+  }
+
+  if (nesting > 0)
+    FillPayload(timings, seq, nesting - 1, payload->add_nested());
+
+  payload->add_str(buf);
+}
+}  // namespace
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(StressTestDataSource);
+
+}  // namespace perfetto
+
+int main() {
+  perfetto::TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+
+  std::string config_blob;
+  if (isatty(STDIN_FILENO))
+    PERFETTO_LOG("Reading StressTestConfig proto from stdin");
+  perfetto::base::ReadFileStream(stdin, &config_blob);
+
+  StressTestConfig cfg;
+  perfetto::g_cfg = &cfg;
+  if (config_blob.empty() || !cfg.ParseFromString(config_blob))
+    PERFETTO_FATAL("A StressTestConfig blob must be passed into stdin");
+
+  if (cfg.shmem_page_size_kb())
+    args.shmem_page_size_hint_kb = cfg.shmem_page_size_kb();
+  if (cfg.shmem_size_kb())
+    args.shmem_page_size_hint_kb = cfg.shmem_size_kb();
+
+  perfetto::Tracing::Initialize(args);
+  perfetto::DataSourceDescriptor dsd;
+  dsd.set_name("perfetto.stress_test");
+  perfetto::StressTestDataSource::Register(dsd);
+
+  for (;;) {
+    pause();
+  }
+}
diff --git a/test/stress_test/stress_test.cc b/test/stress_test/stress_test.cc
new file mode 100644
index 0000000..6215e00
--- /dev/null
+++ b/test/stress_test/stress_test.cc
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+#include <stdarg.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <chrono>
+#include <list>
+#include <map>
+#include <random>
+#include <regex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/subprocess.h"
+#include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/utils.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/tracing.h"
+#include "perfetto/tracing/core/forward_decls.h"
+#include "perfetto/tracing/core/trace_config.h"
+#include "src/base/test/utils.h"
+
+#include "protos/perfetto/config/stress_test_config.gen.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+// Generated by gen_configs_blob.py. It defines the kStressTestConfigs array,
+// which contains a proto-encoded StressTestConfig message for each .cfg file
+// listed in /test/stress_test/configs/BUILD.gn.
+#include "test/stress_test/configs/stress_test_config_blobs.h"
+
+namespace perfetto {
+namespace {
+
+using StressTestConfig = protos::gen::StressTestConfig;
+
+struct SigHandlerCtx {
+  std::atomic<bool> aborted{};
+  std::vector<int> pids_to_kill;
+};
+SigHandlerCtx* g_sig;
+
+struct TestResult {
+  const char* cfg_name = nullptr;
+  StressTestConfig cfg;
+  uint32_t run_time_ms = 0;
+  uint32_t trace_size_kb = 0;
+  uint32_t num_packets = 0;
+  uint32_t num_threads = 0;
+  uint32_t num_errors = 0;
+  base::Subprocess::ResourceUsage svc_rusage;
+  base::Subprocess::ResourceUsage prod_rusage;
+};
+
+struct ParsedTraceStats {
+  struct WriterThread {
+    uint64_t packets_seen = 0;
+    bool last_seen = false;
+    uint32_t last_seq = 0;
+    uint64_t seq_errors = 0;
+    uint64_t counter_errors = 0;
+    std::minstd_rand0 rnd_engine;
+  };
+
+  // One for each trusted_packet_sequence_id.
+  std::map<uint32_t, WriterThread> threads;
+};
+
+class TestHarness {
+ public:
+  TestHarness();
+  void RunConfig(const char* cfg_name, const StressTestConfig&, bool verbose);
+  const std::list<TestResult>& test_results() const { return test_results_; }
+
+ private:
+  void ReadbackTrace(const std::string&, ParsedTraceStats*);
+  void ParseTracePacket(const uint8_t*, size_t, ParsedTraceStats* ctx);
+  void AddFailure(const char* fmt, ...) PERFETTO_PRINTF_FORMAT(2, 3);
+
+  std::vector<std::string> env_;
+  std::list<TestResult> test_results_;
+  std::string results_dir_;
+  base::ScopedFile error_log_;
+};
+
+TestHarness::TestHarness() {
+  results_dir_ = base::GetSysTempDir() + "/perfetto-stress-test";
+  system(("rm -r -- \"" + results_dir_ + "\"").c_str());
+  PERFETTO_CHECK(base::Mkdir(results_dir_));
+  PERFETTO_LOG("Saving test results in %s", results_dir_.c_str());
+}
+
+void TestHarness::AddFailure(const char* fmt, ...) {
+  ++test_results_.back().num_errors;
+
+  char log_msg[512];
+  va_list args;
+  va_start(args, fmt);
+  int res = vsnprintf(log_msg, sizeof(log_msg), fmt, args);
+  va_end(args);
+
+  PERFETTO_ELOG("FAIL: %s", log_msg);
+
+  if (res > 0 && static_cast<size_t>(res) < sizeof(log_msg) - 2) {
+    log_msg[res++] = '\n';
+    log_msg[res++] = '\0';
+  }
+  base::ignore_result(write(*error_log_, log_msg, static_cast<size_t>(res)));
+}
+
+void TestHarness::RunConfig(const char* cfg_name,
+                            const StressTestConfig& cfg,
+                            bool verbose) {
+  test_results_.emplace_back();
+  TestResult& test_result = test_results_.back();
+  test_result.cfg_name = cfg_name;
+  test_result.cfg = cfg;
+  g_sig->pids_to_kill.clear();
+
+  auto result_dir = results_dir_ + "/" + cfg_name;
+  PERFETTO_CHECK(base::Mkdir(result_dir));
+  error_log_ = base::OpenFile(result_dir + "/errors.log",
+                              O_RDWR | O_CREAT | O_TRUNC, 0644);
+
+  PERFETTO_ILOG("Starting \"%s\" - %s", cfg_name, result_dir.c_str());
+
+  env_.emplace_back("PERFETTO_PRODUCER_SOCK_NAME=" + result_dir +
+                    "/producer.sock");
+  env_.emplace_back("PERFETTO_CONSUMER_SOCK_NAME=" + result_dir +
+                    "/consumer.sock");
+  std::string bin_dir = base::GetCurExecutableDir();
+
+  // Start the service.
+  base::Subprocess traced({bin_dir + "/traced"});
+  traced.args.env = env_;
+  if (!verbose) {
+    traced.args.out_fd = base::OpenFile(result_dir + "/traced.log",
+                                        O_RDWR | O_CREAT | O_TRUNC, 0644);
+    traced.args.stderr_mode = traced.args.stdout_mode = base::Subprocess::kFd;
+  }
+  traced.Start();
+  g_sig->pids_to_kill.emplace_back(traced.pid());
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  PERFETTO_CHECK(traced.Poll() == base::Subprocess::kRunning);
+
+  // Start the producer processes.
+  std::list<base::Subprocess> producers;
+  for (uint32_t i = 0; i < cfg.num_processes(); ++i) {
+    producers.emplace_back(base::Subprocess({bin_dir + "/stress_producer"}));
+    auto& producer = producers.back();
+    producer.args.input = cfg.SerializeAsString();
+    if (!verbose) {
+      producer.args.out_fd =
+          base::OpenFile(result_dir + "/producer." + std::to_string(i) + ".log",
+                         O_RDWR | O_CREAT | O_TRUNC, 0644);
+      producer.args.stderr_mode = producer.args.stdout_mode =
+          base::Subprocess::kFd;
+    }
+    producer.args.env = env_;
+    producer.Start();
+    g_sig->pids_to_kill.emplace_back(producer.pid());
+  }
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  for (auto& producer : producers)
+    PERFETTO_CHECK(producer.Poll() == base::Subprocess::kRunning);
+
+  auto trace_file_path = result_dir + "/trace";
+  base::Subprocess consumer(
+      {bin_dir + "/perfetto", "-c", "-", "-o", trace_file_path.c_str()});
+  consumer.args.env = env_;
+  consumer.args.input = cfg.trace_config().SerializeAsString();
+  if (!verbose) {
+    consumer.args.out_fd = base::OpenFile(result_dir + "/perfetto.log",
+                                          O_RDWR | O_CREAT | O_TRUNC, 0644);
+    consumer.args.stderr_mode = consumer.args.stdout_mode =
+        base::Subprocess::kFd;
+  }
+  unlink(trace_file_path.c_str());
+  consumer.Start();
+  int64_t t_start = base::GetBootTimeNs().count();
+  g_sig->pids_to_kill.emplace_back(consumer.pid());
+
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  PERFETTO_CHECK(consumer.Poll() == base::Subprocess::kRunning);
+
+  if (!consumer.Wait(
+          static_cast<int>(cfg.trace_config().duration_ms() + 30000))) {
+    AddFailure("Consumer didn't quit in time");
+    consumer.KillAndWaitForTermination();
+  }
+
+  // Stop
+  consumer.KillAndWaitForTermination(SIGTERM);
+  int64_t t_end = base::GetBootTimeNs().count();
+
+  for (auto& producer : producers) {
+    producer.KillAndWaitForTermination();
+    test_result.prod_rusage = producer.rusage();  // Only keep last one
+  }
+  producers.clear();
+  traced.KillAndWaitForTermination();
+
+  test_result.svc_rusage = traced.rusage();
+  test_result.run_time_ms = static_cast<uint32_t>((t_end - t_start) / 1000000);
+
+  // Verify
+  // TODO(primiano): read back the TraceStats and check them as well.
+  ParsedTraceStats ctx;
+  ReadbackTrace(trace_file_path, &ctx);
+  auto exp_thd = cfg.num_processes() * cfg.num_threads();
+  if (ctx.threads.size() != exp_thd) {
+    AddFailure("Trace threads mismatch. Expected %u threads, got %zu", exp_thd,
+               ctx.threads.size());
+  }
+  for (const auto& it : ctx.threads) {
+    uint32_t seq_id = it.first;
+    const auto& thd = it.second;
+    if (!thd.last_seen) {
+      AddFailure("Last packet not seen for sequence %u", seq_id);
+    }
+    if (thd.seq_errors > 0) {
+      AddFailure("Sequence %u had %" PRIu64 " packets out of sync", seq_id,
+                 thd.seq_errors);
+    }
+    if (thd.counter_errors > 0) {
+      AddFailure("Sequence %u had %" PRIu64 " packets counter errors", seq_id,
+                 thd.counter_errors);
+    }
+  }
+
+  error_log_.reset();
+  PERFETTO_ILOG("Completed \"%s\"", cfg_name);
+}
+
+void TestHarness::ReadbackTrace(const std::string& trace_file_path,
+                                ParsedTraceStats* ctx) {
+  TestResult& test_result = test_results_.back();
+  using namespace protozero::proto_utils;
+  auto fd = base::OpenFile(trace_file_path.c_str(), O_RDONLY);
+  if (!fd)
+    return AddFailure("Trace file does not exist");
+  const off_t file_size = lseek(*fd, 0, SEEK_END);
+  if (file_size <= 0)
+    return AddFailure("Trace file is empty");
+  test_result.trace_size_kb = static_cast<uint32_t>(file_size / 1000);
+  const uint8_t* const start = static_cast<const uint8_t*>(mmap(
+      nullptr, static_cast<size_t>(file_size), PROT_READ, MAP_PRIVATE, *fd, 0));
+  PERFETTO_CHECK(start != MAP_FAILED);
+  const uint8_t* const end = start + file_size;
+
+  constexpr uint8_t kTracePacketTag = MakeTagLengthDelimited(1);
+
+  for (auto* ptr = start; (end - ptr) > 2;) {
+    const uint8_t* tokenizer_start = ptr;
+    if (*(ptr++) != kTracePacketTag) {
+      return AddFailure("Tokenizer failure at offset %zd", ptr - start);
+    }
+    uint64_t packet_size = 0;
+    ptr = ParseVarInt(ptr, end, &packet_size);
+    const uint8_t* const packet_start = ptr;
+    ptr += packet_size;
+    if ((ptr - tokenizer_start) < 2 || ptr > end)
+      return AddFailure("Got invalid packet size %" PRIu64 " at offset %zd",
+                        packet_size,
+                        static_cast<ssize_t>(packet_start - start));
+    ParseTracePacket(packet_start, static_cast<size_t>(packet_size), ctx);
+  }
+  test_result.num_threads = static_cast<uint32_t>(ctx->threads.size());
+}
+
+void TestHarness::ParseTracePacket(const uint8_t* start,
+                                   size_t size,
+                                   ParsedTraceStats* ctx) {
+  TestResult& test_result = test_results_.back();
+  protos::pbzero::TracePacket::Decoder packet(start, size);
+  if (!packet.has_for_testing())
+    return;
+
+  ++test_result.num_packets;
+  const uint32_t seq_id = packet.trusted_packet_sequence_id();
+
+  protos::pbzero::TestEvent::Decoder te(packet.for_testing());
+  auto t_it = ctx->threads.find(seq_id);
+  bool is_first_packet = false;
+  if (t_it == ctx->threads.end()) {
+    is_first_packet = true;
+    t_it = ctx->threads.emplace(seq_id, ParsedTraceStats::WriterThread()).first;
+  }
+  ParsedTraceStats::WriterThread& thd = t_it->second;
+
+  ++thd.packets_seen;
+  if (te.is_last()) {
+    if (thd.last_seen) {
+      return AddFailure(
+          "last_seen=true happened more than once for sequence %u", seq_id);
+    } else {
+      thd.last_seen = true;
+    }
+  }
+  if (is_first_packet) {
+    thd.rnd_engine = std::minstd_rand0(te.seq_value());
+  } else {
+    const uint32_t expected = static_cast<uint32_t>(thd.rnd_engine());
+    if (te.seq_value() != expected) {
+      thd.rnd_engine = std::minstd_rand0(te.seq_value());  // Resync the engine.
+      ++thd.seq_errors;
+      return AddFailure(
+          "TestEvent seq mismatch for sequence %u. Expected %u got %u", seq_id,
+          expected, te.seq_value());
+    }
+    if (te.counter() != thd.packets_seen) {
+      return AddFailure(
+          "TestEvent counter mismatch for sequence %u. Expected %" PRIu64
+          " got %" PRIu64,
+          seq_id, thd.packets_seen, te.counter());
+    }
+  }
+
+  if (!te.has_payload()) {
+    return AddFailure("TestEvent %u for sequence %u has no payload",
+                      te.seq_value(), seq_id);
+  }
+
+  // Check the validity of the payload. The payload might be nested. If that is
+  // the case, we need to check all levels.
+  protozero::ConstBytes payload_bounds = te.payload();
+  for (uint32_t depth = 0, last_depth = 0;; depth++) {
+    if (depth > 100) {
+      return AddFailure("Unexpectedly deep depth for event %u, sequence %u",
+                        te.seq_value(), seq_id);
+    }
+    protos::pbzero::TestEvent::TestPayload::Decoder payload(payload_bounds);
+    const uint32_t rem_depth = payload.remaining_nesting_depth();
+
+    // The payload is a repeated field and must have exactly two instances.
+    // The writer splits it always in two halves of identical size.
+    int num_payload_pieces = 0;
+    size_t last_size = 0;
+    for (auto it = payload.str(); it; ++it, ++num_payload_pieces) {
+      protozero::ConstChars payload_str = *it;
+      last_size = last_size ? last_size : payload_str.size;
+      if (payload_str.size != last_size) {
+        return AddFailure(
+            "Asymmetrical payload at depth %u, event id %u, sequence %u. "
+            "%zu != %zu",
+            depth, te.seq_value(), seq_id, last_size, payload_str.size);
+      }
+      // Check that the payload content matches the expected sequence.
+      for (size_t i = 0; i < payload_str.size; i++) {
+        char exp = static_cast<char>(33 + ((te.seq_value() + i) % 64));
+        if (payload_str.data[i] != exp) {
+          return AddFailure(
+              "Payload mismatch at %zu, depth %u, event id %u, sequence %u. "
+              "Expected: 0x%x, Actual: 0x%x",
+              i, depth, te.seq_value(), seq_id, exp, payload_str.data[i]);
+        }
+      }
+    }
+    if (num_payload_pieces != 2) {
+      return AddFailure(
+          "Broken payload at depth %u, event id %u, sequence %u. "
+          "Expecting 2 repeated str fields, got %d",
+          depth, te.seq_value(), seq_id, num_payload_pieces);
+    }
+
+    if (depth > 0 && rem_depth != last_depth - 1) {
+      return AddFailure(
+          "Unexpected nesting level (expected: %u, actual: %u) at depth %u, "
+          "event id %u, sequence %u",
+          rem_depth, last_depth - 1, depth, te.seq_value(), seq_id);
+    }
+
+    last_depth = rem_depth;
+    if (rem_depth == 0)
+      break;
+    if (payload.has_nested()) {
+      payload_bounds = *payload.nested();
+    } else {
+      payload_bounds = {nullptr, 0};
+    }
+  }
+}
+
+void CtrlCHandler(int) {
+  g_sig->aborted.store(true);
+  for (auto it = g_sig->pids_to_kill.rbegin(); it != g_sig->pids_to_kill.rend();
+       it++) {
+    kill(*it, SIGKILL);
+  }
+}
+
+void StressTestMain(int argc, char** argv) {
+  TestHarness th;
+  std::regex filter;
+  bool has_filter = false;
+
+  bool verbose = false;
+  for (int i = 1; i < argc; ++i) {
+    if (!strcmp(argv[i], "-v")) {
+      verbose = true;
+    } else {
+      filter = std::regex(argv[i], std::regex::ECMAScript | std::regex::icase);
+      has_filter = true;
+    }
+  }
+
+  g_sig = new SigHandlerCtx();
+  signal(SIGINT, CtrlCHandler);
+
+  for (size_t i = 0; i < base::ArraySize(kStressTestConfigs) && !g_sig->aborted;
+       ++i) {
+    const auto& cfg_blob = kStressTestConfigs[i];
+    StressTestConfig cfg;
+    std::cmatch ignored;
+    if (has_filter && !std::regex_search(cfg_blob.name, ignored, filter)) {
+      continue;
+    }
+    PERFETTO_CHECK(cfg.ParseFromArray(cfg_blob.data, cfg_blob.size));
+    th.RunConfig(cfg_blob.name, cfg, verbose);
+  }
+
+  for (const auto& tres : th.test_results()) {
+    const auto& cfg = tres.cfg;
+    printf("===============================================================\n");
+    printf("Config: %s\n", tres.cfg_name);
+    printf("===============================================================\n");
+    printf("%-20s %-10s %-10s\n", "Metric", "Expected", "Actual");
+    printf("%-20s %-10s %-10s\n", "------", "--------", "------");
+    printf("%-20s %-10d %-10d\n", "#Errors", 0, tres.num_errors);
+    printf("%-20s %-10d %-10d \n", "Duration [ms]",
+           cfg.trace_config().duration_ms(), tres.run_time_ms);
+
+    uint32_t exp_threads = cfg.num_processes() * cfg.num_threads();
+    printf("%-20s %-10u %-10u\n", "Num threads", exp_threads, tres.num_threads);
+
+    double dur_s = cfg.trace_config().duration_ms() / 1e3;
+    double exp_per_thread = cfg.steady_state_timings().rate_mean() * dur_s;
+    if (cfg.burst_period_ms()) {
+      double burst_rate = 1.0 * cfg.burst_duration_ms() / cfg.burst_period_ms();
+      exp_per_thread *= 1.0 - burst_rate;
+      exp_per_thread += burst_rate * cfg.burst_timings().rate_mean() * dur_s;
+    }
+    if (cfg.max_events())
+      exp_per_thread = std::min(exp_per_thread, 1.0 * cfg.max_events());
+    double exp_packets = std::round(exp_per_thread * exp_threads);
+    printf("%-20s %-10.0f %-10d\n", "Num packets", exp_packets,
+           tres.num_packets);
+
+    double exp_size_kb = exp_packets * (cfg.nesting() + 1) *
+                         (cfg.steady_state_timings().payload_mean() + 40) /
+                         1000;
+    printf("%-20s ~%-9.0f %-10d\n", "Trace size [KB]", exp_size_kb,
+           tres.trace_size_kb);
+
+    double exp_rss_mb = cfg.trace_config().buffers()[0].size_kb() / 1000;
+    printf("%-20s (max) %-4.0f %-10d\n", "Svc RSS [MB]", exp_rss_mb,
+           tres.svc_rusage.max_rss_kb / 1000);
+    printf("%-20s %-10s %-10d\n", "Svc CPU [ms]", "---",
+           tres.svc_rusage.cpu_time_ms());
+    printf("%-20s %-10s %d / %d\n", "Svc #ctxswitch", "---",
+           tres.svc_rusage.invol_ctx_switch, tres.svc_rusage.vol_ctx_switch);
+
+    printf("%-20s %-10s %-10d\n", "Prod RSS [MB]", "---",
+           tres.prod_rusage.max_rss_kb / 1000);
+    printf("%-20s %-10s %-10d\n", "Prod CPU [ms]", "---",
+           tres.prod_rusage.cpu_time_ms());
+    printf("%-20s %-10s %d / %d\n", "Prod #ctxswitch", "---",
+           tres.prod_rusage.invol_ctx_switch, tres.prod_rusage.vol_ctx_switch);
+    printf("\n");
+  }
+}
+
+}  // namespace
+}  // namespace perfetto
+
+int main(int argc, char** argv) {
+  perfetto::StressTestMain(argc, argv);
+}
diff --git a/test/synth_common.py b/test/synth_common.py
index e9ec22f..7fe68fd 100644
--- a/test/synth_common.py
+++ b/test/synth_common.py
@@ -22,6 +22,34 @@
 CLONE_VFORK = 0x00004000
 CLONE_VM = 0x00000100
 
+# TODO(b/174825244): These magic numbers should go away.
+TYPE_SLICE_BEGIN = 1
+TYPE_SLICE_END = 2
+
+RAIL_MODE_RESPONSE = 1
+RAIL_MODE_ANIMATION = 2
+RAIL_MODE_IDLE = 3
+RAIL_MODE_LOAD = 4
+
+PROCESS_BROWSER = 1
+PROCESS_RENDERER = 2
+PROCESS_GPU = 6
+
+CHROME_THREAD_UNSPECIFIED = 0
+CHROME_THREAD_MAIN = 1
+CHROME_THREAD_IO = 2
+CHROME_THREAD_COMPOSITOR = 8
+
+COUNTER_THREAD_TIME_NS = 1
+
+
+def ms_to_ns(time_in_ms):
+  return int(time_in_ms * 1000000)
+
+
+def s_to_ns(time_in_s):
+  return int(time_in_s * 1000000000)
+
 
 class Trace(object):
 
@@ -187,10 +215,12 @@
       process.uid = uid
     self.proc_map[pid] = cmdline
 
-  def add_thread(self, tid, tgid, cmdline):
+  def add_thread(self, tid, tgid, cmdline, name=None):
     thread = self.packet.process_tree.threads.add()
     thread.tid = tid
     thread.tgid = tgid
+    if name is not None:
+      thread.name = name
     self.proc_map[tid] = cmdline
 
   def add_battery_counters(self, ts, charge_uah, cap_prct, curr_ua,
@@ -393,12 +423,207 @@
       thread.cpu_freq_indices.append(index)
       thread.cpu_freq_ticks.append(freqs[index])
 
-  def add_gpu_mem_total(self, pid, ts, size):
+  def add_gpu_mem_total_ftrace_event(self, pid, ts, size):
     ftrace = self.__add_ftrace_event(ts, pid)
-    gpu_mem_total_event = ftrace.gpu_mem_total
+    gpu_mem_total_ftrace_event = ftrace.gpu_mem_total
+    gpu_mem_total_ftrace_event.pid = pid
+    gpu_mem_total_ftrace_event.size = size
+
+  def add_gpu_mem_total_event(self, pid, ts, size):
+    packet = self.add_packet()
+    packet.timestamp = ts
+    gpu_mem_total_event = packet.gpu_mem_total_event
     gpu_mem_total_event.pid = pid
     gpu_mem_total_event.size = size
 
+  def add_sched_blocked_reason(self, ts, pid, io_wait, unblock_pid):
+    ftrace = self.__add_ftrace_event(ts, unblock_pid)
+    sched_blocked_reason = ftrace.sched_blocked_reason
+    sched_blocked_reason.pid = pid
+    sched_blocked_reason.io_wait = io_wait
+
+  def add_track_event(self,
+                      name=None,
+                      ts=None,
+                      track=None,
+                      trusted_sequence_id=0,
+                      cpu_time=None):
+    packet = self.add_packet(ts=ts)
+    if name is not None:
+      packet.track_event.name = name
+    if track is not None:
+      packet.track_event.track_uuid = track
+    packet.trusted_packet_sequence_id = trusted_sequence_id
+    if cpu_time is not None:
+      packet.track_event.extra_counter_values.append(cpu_time)
+    return packet
+
+  def add_track_descriptor(self, uuid, parent=None):
+    packet = self.add_packet()
+    track_descriptor = packet.track_descriptor
+    if uuid is not None:
+      track_descriptor.uuid = uuid
+    if parent is not None:
+      track_descriptor.parent_uuid = parent
+    return packet
+
+  def add_process_track_descriptor(self, process_track, pid=None):
+    packet = self.add_track_descriptor(process_track)
+    packet.track_descriptor.process.pid = pid
+    return packet
+
+  def add_chrome_process_track_descriptor(
+      self,
+      process_track,
+      pid=None,
+      process_type=PROCESS_RENDERER,
+      host_app_package_name="org.chromium.chrome"):
+    packet = self.add_process_track_descriptor(process_track, pid=pid)
+    chrome_process = packet.track_descriptor.chrome_process
+    chrome_process.process_type = process_type
+    chrome_process.host_app_package_name = host_app_package_name
+    return packet
+
+  def add_thread_track_descriptor(self,
+                                  process_track,
+                                  thread_track,
+                                  trusted_packet_sequence_id=None,
+                                  pid=None,
+                                  tid=None):
+    packet = self.add_track_descriptor(thread_track, parent=process_track)
+    packet.trusted_packet_sequence_id = trusted_packet_sequence_id
+    packet.track_descriptor.thread.pid = pid
+    packet.track_descriptor.thread.tid = tid
+    return packet
+
+  def add_chrome_thread_track_descriptor(self,
+                                         process_track,
+                                         thread_track,
+                                         trusted_packet_sequence_id=None,
+                                         pid=None,
+                                         tid=None,
+                                         thread_type=CHROME_THREAD_UNSPECIFIED):
+    packet = self.add_thread_track_descriptor(
+        process_track,
+        thread_track,
+        trusted_packet_sequence_id=trusted_packet_sequence_id,
+        pid=pid,
+        tid=tid)
+    return packet
+
+  def add_trace_packet_defaults(self,
+                                trusted_packet_sequence_id=None,
+                                thread_track=None,
+                                counter_track=None):
+    packet = self.add_track_descriptor(None)
+    packet.trusted_packet_sequence_id = trusted_packet_sequence_id
+    track_event_defaults = packet.trace_packet_defaults.track_event_defaults
+    track_event_defaults.track_uuid = thread_track
+    track_event_defaults.extra_counter_track_uuids.append(counter_track)
+    return packet
+
+  def add_counter_track_descriptor(self,
+                                   trusted_packet_sequence_id=None,
+                                   thread_track=None,
+                                   counter_track=None):
+    packet = self.add_track_descriptor(counter_track, parent=thread_track)
+    packet.trusted_packet_sequence_id = trusted_packet_sequence_id
+    packet.track_descriptor.counter.type = COUNTER_THREAD_TIME_NS
+    return packet
+
+  def add_chrome_thread_with_cpu_counter(self,
+                                         process_track,
+                                         thread_track,
+                                         trusted_packet_sequence_id=None,
+                                         counter_track=None,
+                                         pid=None,
+                                         tid=None,
+                                         thread_type=None):
+    self.add_chrome_thread_track_descriptor(
+        process_track,
+        thread_track,
+        trusted_packet_sequence_id=trusted_packet_sequence_id,
+        pid=pid,
+        tid=tid,
+        thread_type=thread_type)
+    self.add_trace_packet_defaults(
+        trusted_packet_sequence_id=trusted_packet_sequence_id,
+        counter_track=counter_track,
+        thread_track=thread_track)
+
+    self.add_counter_track_descriptor(
+        trusted_packet_sequence_id=trusted_packet_sequence_id,
+        counter_track=counter_track,
+        thread_track=thread_track)
+
+  def add_track_event_slice_begin(self,
+                                  name,
+                                  ts,
+                                  track=None,
+                                  trusted_sequence_id=0,
+                                  cpu_time=None):
+    packet = self.add_track_event(
+        name,
+        ts=ts,
+        track=track,
+        trusted_sequence_id=trusted_sequence_id,
+        cpu_time=cpu_time)
+    packet.track_event.type = TYPE_SLICE_BEGIN
+    return packet
+
+  def add_track_event_slice_end(self,
+                                ts,
+                                track=None,
+                                trusted_sequence_id=0,
+                                cpu_time=None):
+    packet = self.add_track_event(
+        ts=ts,
+        track=track,
+        trusted_sequence_id=trusted_sequence_id,
+        cpu_time=cpu_time)
+    packet.track_event.type = TYPE_SLICE_END
+    return packet
+
+  # Returns the start slice packet.
+  def add_track_event_slice(self,
+                            name,
+                            ts,
+                            dur,
+                            track=None,
+                            trusted_sequence_id=0,
+                            cpu_start=None,
+                            cpu_delta=None):
+
+    packet = self.add_track_event_slice_begin(
+        name,
+        ts,
+        track=track,
+        trusted_sequence_id=trusted_sequence_id,
+        cpu_time=cpu_start)
+
+    if dur >= 0:
+      cpu_end = cpu_start + cpu_delta if cpu_start is not None else None
+      self.add_track_event_slice_end(
+          ts + dur,
+          track=track,
+          trusted_sequence_id=trusted_sequence_id,
+          cpu_time=cpu_end)
+
+    return packet
+
+  def add_rail_mode_slice(self, ts, dur, track, mode):
+    packet = self.add_track_event_slice(
+        "Scheduler.RAILMode", ts=ts, dur=dur, track=track)
+    packet.track_event.chrome_renderer_scheduler_state.rail_mode = mode
+
+  def add_chrome_metadata(self, os_name=None):
+    metadata = self.add_packet().chrome_events.metadata.add()
+    if os_name is not None:
+      metadata.name = "os-name"
+      metadata.string_value = os_name
+
+    return metadata
+
 
 def create_trace():
   parser = argparse.ArgumentParser()
diff --git a/test/test_helper.cc b/test/test_helper.cc
index f397e8b..9885e84 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -16,7 +16,6 @@
 
 #include "test/test_helper.h"
 
-#include "perfetto/ext/traced/traced.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
 #include "perfetto/ext/tracing/ipc/default_socket.h"
 #include "perfetto/tracing/core/tracing_service_state.h"
@@ -55,8 +54,9 @@
   PERFETTO_FATAL("Consumer unexpectedly disconnected from the service");
 }
 
-void TestHelper::OnTracingDisabled() {
+void TestHelper::OnTracingDisabled(const std::string& /*error*/) {
   std::move(on_stop_tracing_callback_)();
+  on_stop_tracing_callback_ = nullptr;
 }
 
 void TestHelper::OnTraceData(std::vector<TracePacket> packets, bool has_more) {
@@ -64,6 +64,7 @@
     protos::gen::TracePacket packet;
     PERFETTO_CHECK(
         packet.ParseFromString(encoded_packet.GetRawBytesForTesting()));
+    full_trace_.push_back(packet);
     if (packet.has_clock_snapshot() || packet.has_trace_config() ||
         packet.has_trace_stats() || !packet.synchronization_marker().empty() ||
         packet.has_system_info() || packet.has_service_event()) {
@@ -78,9 +79,13 @@
   }
 }
 
+void TestHelper::StartService() {
+  service_thread_.Start();
+}
+
 void TestHelper::StartServiceIfRequired() {
 #if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
-  service_thread_.Start();
+  StartService();
 #endif
 }
 
@@ -119,6 +124,18 @@
   return success;
 }
 
+bool TestHelper::SaveTraceForBugreportAndWait() {
+  bool success = false;
+  auto checkpoint = CreateCheckpoint("bugreport");
+  auto callback = [&success, checkpoint](bool s, const std::string&) {
+    success = s;
+    checkpoint();
+  };
+  endpoint_->SaveTraceForBugreport(callback);
+  RunUntilCheckpoint("bugreport");
+  return success;
+}
+
 void TestHelper::CreateProducerProvidedSmb() {
   fake_producer_thread_.CreateProducerProvidedSmb();
 }
@@ -137,8 +154,10 @@
 
 void TestHelper::StartTracing(const TraceConfig& config,
                               base::ScopedFile file) {
+  PERFETTO_CHECK(!on_stop_tracing_callback_);
   trace_.clear();
-  on_stop_tracing_callback_ = CreateCheckpoint("stop.tracing");
+  on_stop_tracing_callback_ =
+      CreateCheckpoint("stop.tracing" + std::to_string(++trace_count_));
   endpoint_->EnableTracing(config, std::move(file));
 }
 
@@ -160,6 +179,10 @@
   endpoint_->ReadBuffers();
 }
 
+void TestHelper::FreeBuffers() {
+  endpoint_->FreeBuffers();
+}
+
 void TestHelper::WaitForConsumerConnect() {
   RunUntilCheckpoint("consumer.connected." + std::to_string(cur_consumer_num_));
 }
@@ -173,7 +196,8 @@
 }
 
 void TestHelper::WaitForTracingDisabled(uint32_t timeout_ms) {
-  RunUntilCheckpoint("stop.tracing", timeout_ms);
+  RunUntilCheckpoint(std::string("stop.tracing") + std::to_string(trace_count_),
+                     timeout_ms);
 }
 
 void TestHelper::WaitForReadData(uint32_t read_count, uint32_t timeout_ms) {
diff --git a/test/test_helper.h b/test/test_helper.h
index 0e1cc62..d1cce2b 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -34,6 +34,11 @@
 
 namespace perfetto {
 
+// This value has been bumped to 10s in Oct 2020 because the x86 cuttlefish
+// emulator is sensibly slower (up to 10x) than real hw and caused flakes.
+// See bugs duped against b/171771440.
+constexpr uint32_t kDefaultTestTimeoutMs = 10000;
+
 // This is used only in daemon starting integrations tests.
 class ServiceThread {
  public:
@@ -53,7 +58,10 @@
       svc_ = ServiceIPCHost::CreateInstance(runner_->get());
       unlink(producer_socket_.c_str());
       unlink(consumer_socket_.c_str());
-
+      setenv("PERFETTO_PRODUCER_SOCK_NAME", producer_socket_.c_str(),
+             /*overwrite=*/true);
+      setenv("PERFETTO_CONSUMER_SOCK_NAME", consumer_socket_.c_str(),
+             /*overwrite=*/true);
       bool res =
           svc_->Start(producer_socket_.c_str(), consumer_socket_.c_str());
       PERFETTO_CHECK(res);
@@ -133,8 +141,7 @@
   void CreateProducerProvidedSmb() {
     PosixSharedMemory::Factory factory;
     shm_ = factory.CreateSharedMemory(1024 * 1024);
-    shm_arbiter_ =
-        SharedMemoryArbiter::CreateUnboundInstance(shm_.get(), base::kPageSize);
+    shm_arbiter_ = SharedMemoryArbiter::CreateUnboundInstance(shm_.get(), 4096);
   }
 
   void ProduceStartupEventBatch(const protos::gen::TestConfig& config,
@@ -165,13 +172,18 @@
   // Consumer implementation.
   void OnConnect() override;
   void OnDisconnect() override;
-  void OnTracingDisabled() override;
+  void OnTracingDisabled(const std::string& error) override;
   void OnTraceData(std::vector<TracePacket> packets, bool has_more) override;
   void OnDetach(bool) override;
   void OnAttach(bool, const TraceConfig&) override;
   void OnTraceStats(bool, const TraceStats&) override;
   void OnObservableEvents(const ObservableEvents&) override;
 
+  // Starts the tracing service unconditionally.
+  void StartService();
+
+  // Starts the tracing service unless the build was configured to use an
+  // existing one running on the system.
   void StartServiceIfRequired();
 
   // Connects the producer and waits that the service has seen the
@@ -184,8 +196,10 @@
   void DisableTracing();
   void FlushAndWait(uint32_t timeout_ms);
   void ReadData(uint32_t read_count = 0);
+  void FreeBuffers();
   void DetachConsumer(const std::string& key);
   bool AttachConsumer(const std::string& key);
+  bool SaveTraceForBugreportAndWait();
   void CreateProducerProvidedSmb();
   bool IsShmemProvidedByProducer();
   void ProduceStartupEventBatch(const protos::gen::TestConfig& config);
@@ -193,8 +207,9 @@
   void WaitForConsumerConnect();
   void WaitForProducerSetup();
   void WaitForProducerEnabled();
-  void WaitForTracingDisabled(uint32_t timeout_ms = 5000);
-  void WaitForReadData(uint32_t read_count = 0, uint32_t timeout_ms = 5000);
+  void WaitForTracingDisabled(uint32_t timeout_ms = kDefaultTestTimeoutMs);
+  void WaitForReadData(uint32_t read_count = 0,
+                       uint32_t timeout_ms = kDefaultTestTimeoutMs);
   void SyncAndWaitProducer();
   TracingServiceState QueryServiceStateAndWait();
 
@@ -207,7 +222,7 @@
   }
 
   void RunUntilCheckpoint(const std::string& checkpoint,
-                          uint32_t timeout_ms = 5000) {
+                          uint32_t timeout_ms = kDefaultTestTimeoutMs) {
     return task_runner_->RunUntilCheckpoint(AddID(checkpoint), timeout_ms);
   }
 
@@ -217,6 +232,9 @@
   base::ThreadTaskRunner* producer_thread() {
     return fake_producer_thread_.runner();
   }
+  const std::vector<protos::gen::TracePacket>& full_trace() {
+    return full_trace_;
+  }
   const std::vector<protos::gen::TracePacket>& trace() { return trace_; }
 
  private:
@@ -224,6 +242,7 @@
   uint64_t instance_num_;
   base::TestTaskRunner* task_runner_ = nullptr;
   int cur_consumer_num_ = 0;
+  uint64_t trace_count_ = 0;
 
   std::function<void()> on_connect_callback_;
   std::function<void()> on_packets_finished_callback_;
@@ -231,6 +250,7 @@
   std::function<void()> on_detach_callback_;
   std::function<void(bool)> on_attach_callback_;
 
+  std::vector<protos::gen::TracePacket> full_trace_;
   std::vector<protos::gen::TracePacket> trace_;
 
   ServiceThread service_thread_;
diff --git a/test/trace_processor/chrome/actual_power_by_combined_rail_mode.out b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.out
new file mode 100644
index 0000000..6c6e08c
--- /dev/null
+++ b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.out
@@ -0,0 +1,12 @@
+
+"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
diff --git a/test/trace_processor/chrome/actual_power_by_combined_rail_mode.py b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.py
new file mode 100644
index 0000000..49690f7
--- /dev/null
+++ b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+from synth_common import ms_to_ns
+
+trace = synth_common.create_trace()
+
+process_track1 = 1234
+process_track2 = 4567
+
+process_pid1 = 2345
+process_pid2 = 5678
+
+thread_track1 = 1235
+thread_track2 = 4568
+
+rail_track1 = 1236
+rail_track2 = 4569
+
+# Main threads have the same ID as the process
+thread_tid1 = process_pid1
+thread_tid2 = process_pid2
+
+seq1 = 9876
+seq2 = 9877
+
+thread1_counter = 60
+thread2_counter = 61
+
+packet = trace.add_packet()
+packet = trace.add_power_rails_desc(0, "PPVAR_VPH_PWR_RF")
+packet = trace.add_power_rails_desc(1, "PPVAR_VPH_PWR_S1C")
+
+trace.add_chrome_process_track_descriptor(process_track1, process_pid1)
+trace.add_chrome_process_track_descriptor(process_track2, process_pid2)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track1,
+    thread_track1,
+    trusted_packet_sequence_id=seq1,
+    counter_track=thread1_counter,
+    pid=process_pid1,
+    tid=thread_tid1,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track2,
+    thread_track2,
+    trusted_packet_sequence_id=seq2,
+    counter_track=thread2_counter,
+    pid=process_pid2,
+    tid=thread_tid2,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_track_descriptor(rail_track1, parent=process_track1)
+trace.add_track_descriptor(rail_track2, parent=process_track2)
+
+trace.add_rail_mode_slice(
+    ts=0,
+    dur=ms_to_ns(10),
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(10),
+    dur=ms_to_ns(20),
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(30),
+    dur=-1,
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_track_event_slice(
+    "task",
+    0,
+    ms_to_ns(10),
+    trusted_sequence_id=seq2,
+    cpu_start=0,
+    cpu_delta=ms_to_ns(10))
+
+trace.add_rail_mode_slice(
+    ts=0,
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(10),
+    dur=ms_to_ns(25),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(35),
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(45),
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+packet = trace.add_packet()
+
+# cellular
+packet = trace.add_power_rails_data(0, 0, 0)
+packet = trace.add_power_rails_data(10, 0, 0)
+packet = trace.add_power_rails_data(20, 0, 30)
+packet = trace.add_power_rails_data(30, 0, 50)
+packet = trace.add_power_rails_data(40, 0, 55)
+packet = trace.add_power_rails_data(50, 0, 56)
+packet = trace.add_power_rails_data(55, 0, 56)
+
+# cpu little cores
+packet = trace.add_power_rails_data(0, 1, 0)
+packet = trace.add_power_rails_data(10, 1, 20)
+packet = trace.add_power_rails_data(20, 1, 30)
+packet = trace.add_power_rails_data(30, 1, 40)
+packet = trace.add_power_rails_data(40, 1, 42)
+packet = trace.add_power_rails_data(50, 1, 60)
+packet = trace.add_power_rails_data(55, 1, 61)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/actual_power_by_combined_rail_mode.sql
index cdd953a..9690f17 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/actual_power_by_combined_rail_mode.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,7 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+-- SELECT RUN_METRIC('chrome/chrome_processes.sql') AS suppress_query_output;
+-- SELECT * FROM cpu_time_by_rail_mode;
+SELECT RUN_METRIC('chrome/actual_power_by_rail_mode.sql') AS suppress_query_output;
+SELECT * FROM real_power_by_rail_mode;
diff --git a/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.out b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.out
new file mode 100644
index 0000000..92b2a6d
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.out
@@ -0,0 +1,7 @@
+
+"trace_id","dur","start_cpu_time","end_cpu_time","slice_cpu_time"
+2734,2000,3192921000.000000,3192923000.000000,2000.000000
+2734,258000,3192951000.000000,3193122000.000000,171000.000000
+2734,1000,3193009000.000000,3193010000.000000,1000.000000
+2734,25000,1579266000.000000,1579291000.000000,25000.000000
+2734,1000,1579284000.000000,1579286000.000000,2000.000000
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.sql
similarity index 62%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.sql
index cdd953a..10857de 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time.sql
@@ -14,12 +14,16 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
+SELECT RUN_METRIC('chrome/chrome_thread_slice_with_cpu_time.sql')
+    AS suppress_query_output;
+
 SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') AS trace_id,
+  dur,
+  start_cpu_time,
+  end_cpu_time,
+  slice_cpu_time
+FROM chrome_thread_slice_with_cpu_time
+WHERE
+  name = 'LatencyInfo.Flow' AND
+  EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') = 2734;
diff --git a/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.out b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.out
new file mode 100644
index 0000000..02820f0
--- /dev/null
+++ b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.out
@@ -0,0 +1,7 @@
+
+"name","ts","dur","start_cpu_time","end_cpu_time","slice_cpu_time"
+"event1_on_t1",1000,100,1000000.000000,1010000.000000,10000.000000
+"event2_on_t1",2000,200,2000000.000000,2030000.000000,30000.000000
+"event3_on_t1",2000,200,2000000.000000,2030000.000000,30000.000000
+"event4_on_t1",4000,0,2040000.000000,2040000.000000,0.000000
+"event1_on_t3",4000,100,10000.000000,15000.000000,5000.000000
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.sql
similarity index 68%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.sql
index cdd953a..4702b55 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/chrome_thread_slice_with_cpu_time_repeated.sql
@@ -14,12 +14,14 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
+SELECT RUN_METRIC('chrome/chrome_thread_slice_with_cpu_time.sql')
+    AS suppress_query_output;
+
 SELECT
+  name,
   ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  dur,
+  start_cpu_time,
+  end_cpu_time,
+  slice_cpu_time
+FROM chrome_thread_slice_with_cpu_time
diff --git a/test/trace_processor/chrome/combined_rail_modes.out b/test/trace_processor/chrome/combined_rail_modes.out
new file mode 100644
index 0000000..b1b6a29
--- /dev/null
+++ b/test/trace_processor/chrome/combined_rail_modes.out
@@ -0,0 +1,5 @@
+
+"id","ts","dur","rail_mode"
+1,0,10000,"response"
+2,10000,25000,"animation"
+3,35000,10000,"background"
diff --git a/test/trace_processor/chrome/combined_rail_modes.py b/test/trace_processor/chrome/combined_rail_modes.py
new file mode 100644
index 0000000..4d2e26a
--- /dev/null
+++ b/test/trace_processor/chrome/combined_rail_modes.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+track1 = 1234
+track2 = 4567
+
+trace.add_process_track_descriptor(track1, pid=0)
+trace.add_process_track_descriptor(track2, pid=2)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=10000, track=track1, mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=10000, dur=20000, track=track1, mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=30000, dur=-1, track=track1, mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=10000, track=track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=10000, dur=25000, track=track2, mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=25000, dur=10000, track=track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=35000, dur=10000, track=track2, mode=synth_common.RAIL_MODE_IDLE)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/combined_rail_modes.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/combined_rail_modes.sql
index cdd953a..5b49749 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/combined_rail_modes.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,5 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+SELECT RUN_METRIC('chrome/rail_modes.sql') AS suppress_query_output;
+SELECT * FROM combined_overall_rail_slices;
diff --git a/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.out b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.out
new file mode 100644
index 0000000..3c55cf3
--- /dev/null
+++ b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.out
@@ -0,0 +1,7 @@
+
+"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
diff --git a/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.py b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.py
new file mode 100644
index 0000000..194b0ce
--- /dev/null
+++ b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+
+trace = synth_common.create_trace()
+
+process_track1 = 1234
+process_track2 = 4567
+
+process_pid1 = 2345
+process_pid2 = 5678
+
+thread_track1 = 1235
+thread_track2 = 4568
+
+rail_track1 = 1236
+rail_track2 = 4569
+
+# Main threads have the same ID as the process
+thread_tid1 = process_pid1
+thread_tid2 = process_pid2
+
+seq1 = 9876
+seq2 = 9877
+
+thread1_counter = 60
+thread2_counter = 61
+
+trace.add_chrome_process_track_descriptor(process_track1, process_pid1)
+trace.add_chrome_process_track_descriptor(process_track2, process_pid2)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track1,
+    thread_track1,
+    trusted_packet_sequence_id=seq1,
+    counter_track=thread1_counter,
+    pid=process_pid1,
+    tid=thread_tid1,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track2,
+    thread_track2,
+    trusted_packet_sequence_id=seq2,
+    counter_track=thread2_counter,
+    pid=process_pid2,
+    tid=thread_tid2,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_track_descriptor(rail_track1, parent=process_track1)
+trace.add_track_descriptor(rail_track2, parent=process_track2)
+
+trace.add_track_event_slice(
+    "task", 0, 5000, trusted_sequence_id=seq1, cpu_start=0, cpu_delta=10000)
+trace.add_track_event_slice(
+    "task",
+    5000,
+    5000,
+    trusted_sequence_id=seq1,
+    cpu_start=12000,
+    cpu_delta=4000)
+
+trace.add_track_event_slice(
+    "task",
+    10000,
+    6000,
+    trusted_sequence_id=seq1,
+    cpu_start=18000,
+    cpu_delta=2000)
+trace.add_track_event_slice(
+    "task",
+    16000,
+    4000,
+    trusted_sequence_id=seq1,
+    cpu_start=20000,
+    cpu_delta=7000)
+
+trace.add_track_event_slice(
+    "task",
+    30000,
+    10000,
+    trusted_sequence_id=seq1,
+    cpu_start=30000,
+    cpu_delta=1000)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=10000, track=rail_track1, mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=10000, dur=20000, track=rail_track1, mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=30000, dur=-1, track=rail_track1, mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_track_event_slice(
+    "task", 0, 10000, trusted_sequence_id=seq2, cpu_start=0, cpu_delta=10000)
+
+trace.add_track_event_slice(
+    "task",
+    10000,
+    15000,
+    trusted_sequence_id=seq2,
+    cpu_start=12000,
+    cpu_delta=1000)
+
+trace.add_track_event_slice(
+    "task",
+    35000,
+    10000,
+    trusted_sequence_id=seq2,
+    cpu_start=20000,
+    cpu_delta=20000)
+
+trace.add_track_event_slice(
+    "task",
+    45000,
+    10000,
+    trusted_sequence_id=seq2,
+    cpu_start=40000,
+    cpu_delta=1000)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=10000, track=rail_track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=10000, dur=25000, track=rail_track2, mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=35000,
+    dur=10000,
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=45000, dur=10000, track=rail_track2, mode=synth_common.RAIL_MODE_IDLE)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/cpu_time_by_combined_rail_mode.sql
index cdd953a..b66726a 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/cpu_time_by_combined_rail_mode.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,7 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+-- SELECT RUN_METRIC('chrome/chrome_processes.sql') AS suppress_query_output;
+-- SELECT * FROM cpu_time_by_rail_mode;
+SELECT RUN_METRIC('chrome/cpu_time_by_rail_mode.sql') AS suppress_query_output;
+SELECT * FROM cpu_time_by_rail_mode;
diff --git a/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.out b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.out
new file mode 100644
index 0000000..ebb8e23
--- /dev/null
+++ b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.out
@@ -0,0 +1,7 @@
+
+"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
diff --git a/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.py b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.py
new file mode 100644
index 0000000..93ff19f
--- /dev/null
+++ b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+from synth_common import ms_to_ns
+
+trace = synth_common.create_trace()
+
+process_track1 = 1234
+process_track2 = 4567
+
+process_pid1 = 2345
+process_pid2 = 5678
+
+thread_track1 = 1235
+thread_track2 = 4568
+
+rail_track1 = 1236
+rail_track2 = 4569
+
+# Main threads have the same ID as the process
+thread_tid1 = process_pid1
+thread_tid2 = process_pid2
+
+seq1 = 9876
+seq2 = 9877
+
+thread1_counter = 60
+thread2_counter = 61
+
+packet = trace.add_packet()
+packet.system_info.android_build_fingerprint = "google/sargo/foo"
+
+trace.add_chrome_process_track_descriptor(process_track1, process_pid1)
+trace.add_chrome_process_track_descriptor(process_track2, process_pid2)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track1,
+    thread_track1,
+    trusted_packet_sequence_id=seq1,
+    counter_track=thread1_counter,
+    pid=process_pid1,
+    tid=thread_tid1,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_chrome_thread_with_cpu_counter(
+    process_track2,
+    thread_track2,
+    trusted_packet_sequence_id=seq2,
+    counter_track=thread2_counter,
+    pid=process_pid2,
+    tid=thread_tid2,
+    thread_type=synth_common.CHROME_THREAD_MAIN)
+
+trace.add_track_descriptor(rail_track1, parent=process_track1)
+trace.add_track_descriptor(rail_track2, parent=process_track2)
+
+trace.add_rail_mode_slice(
+    ts=0,
+    dur=ms_to_ns(10),
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(10),
+    dur=ms_to_ns(20),
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(30),
+    dur=-1,
+    track=rail_track1,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_rail_mode_slice(
+    ts=0,
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(10),
+    dur=ms_to_ns(25),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(35),
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=ms_to_ns(45),
+    dur=ms_to_ns(10),
+    track=rail_track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+# Create process tree
+trace.add_packet()
+trace.add_process(1, 0, "init")
+trace.add_process(thread_tid1, 1, "Renderer")
+trace.add_process(thread_tid2, 1, "Renderer")
+
+packet = trace.add_packet()
+
+# Add scheduling and cpu frequency data
+trace.add_ftrace_packet(cpu=0)
+trace.add_sched(ts=0, prev_pid=0, next_pid=thread_tid1)
+trace.add_cpufreq(ts=ms_to_ns(0), freq=1708800, cpu=0)
+trace.add_cpufreq(ts=ms_to_ns(5), freq=1324800, cpu=0)
+trace.add_sched(ts=ms_to_ns(20), prev_pid=thread_tid1, next_pid=0)
+trace.add_sched(ts=ms_to_ns(30), prev_pid=0, next_pid=thread_tid1)
+trace.add_cpufreq(ts=ms_to_ns(30), freq=300000, cpu=0)
+trace.add_cpufreq(ts=ms_to_ns(35), freq=1708800, cpu=0)
+trace.add_sched(ts=ms_to_ns(40), prev_pid=thread_tid1, next_pid=0)
+
+trace.add_ftrace_packet(cpu=1)
+trace.add_sched(ts=0, prev_pid=0, next_pid=thread_tid2)
+trace.add_cpufreq(ts=ms_to_ns(0), freq=998400, cpu=1)
+trace.add_sched(ts=ms_to_ns(10), prev_pid=thread_tid2, next_pid=0)
+trace.add_sched(ts=ms_to_ns(35), prev_pid=0, next_pid=thread_tid2)
+trace.add_cpufreq(ts=ms_to_ns(35), freq=1708800, cpu=1)
+trace.add_sched(ts=ms_to_ns(47), prev_pid=thread_tid2, next_pid=0)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/estimated_power_by_combined_rail_mode.sql
index cdd953a..7aab878 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/estimated_power_by_combined_rail_mode.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,7 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+-- SELECT RUN_METRIC('chrome/chrome_processes.sql') AS suppress_query_output;
+-- SELECT * FROM cpu_time_by_rail_mode;
+SELECT RUN_METRIC('chrome/estimated_power_by_rail_mode.sql') AS suppress_query_output;
+SELECT * FROM power_by_rail_mode;
diff --git a/test/trace_processor/chrome/index b/test/trace_processor/chrome/index
index a74aa22..fffc0a8 100644
--- a/test/trace_processor/chrome/index
+++ b/test/trace_processor/chrome/index
@@ -8,3 +8,25 @@
 ../../data/chrome_scroll_without_vsync.pftrace scroll_jank_cause.sql scroll_jank_cause.out
 ../../data/chrome_scroll_without_vsync.pftrace scroll_flow_event_queuing_delay.sql scroll_flow_event_queuing_delay.out
 ../../data/chrome_scroll_without_vsync.pftrace scroll_flow_event_queuing_delay_general_validation.sql scroll_flow_event_general_validation.out
+../../data/chrome_scroll_without_vsync.pftrace scroll_jank_cause_queuing_delay.sql scroll_jank_cause_queuing_delay.out
+../../data/chrome_scroll_without_vsync.pftrace scroll_jank_cause_queuing_delay_general_validation.sql scroll_jank_cause_queuing_delay_general_validation.out
+../../data/chrome_scroll_without_vsync.pftrace chrome_thread_slice_with_cpu_time.sql chrome_thread_slice_with_cpu_time.out
+../track_event/track_event_counters.textproto chrome_thread_slice_with_cpu_time_repeated.sql chrome_thread_slice_with_cpu_time_repeated.out
+
+# Chrome memory snapshots.
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_general_validation.sql memory_snapshot_general_validation.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_os_dump_events.sql memory_snapshot_os_dump_events.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_chrome_dump_events.sql memory_snapshot_chrome_dump_events.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_nodes.sql memory_snapshot_nodes.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_edges.sql memory_snapshot_edges.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_node_args.sql memory_snapshot_node_args.out
+../../data/chrome_memory_snapshot.pftrace memory_snapshot_smaps.sql memory_snapshot_smaps.out
+
+# RAIL modes.
+combined_rail_modes.py combined_rail_modes.sql combined_rail_modes.out
+cpu_time_by_combined_rail_mode.py cpu_time_by_combined_rail_mode.sql cpu_time_by_combined_rail_mode.out
+actual_power_by_combined_rail_mode.py actual_power_by_combined_rail_mode.sql actual_power_by_combined_rail_mode.out
+estimated_power_by_combined_rail_mode.py estimated_power_by_combined_rail_mode.sql estimated_power_by_combined_rail_mode.out
+modified_rail_modes.py modified_rail_modes.sql modified_rail_modes.out
+modified_rail_modes_no_vsyncs.py modified_rail_modes.sql modified_rail_modes_no_vsyncs.out
+modified_rail_modes_with_input.py modified_rail_modes_with_input.sql modified_rail_modes_with_input.out
diff --git a/test/trace_processor/chrome/memory_snapshot_chrome_dump_events.out b/test/trace_processor/chrome/memory_snapshot_chrome_dump_events.out
new file mode 100644
index 0000000..1d07a47
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_chrome_dump_events.out
@@ -0,0 +1,19 @@
+"process_snapshot_id","upid","snapshot_id","timestamp","detail_level"
+0,1,0,111027205123000,"background"
+1,2,0,111027205123000,"background"
+2,3,0,111027205123000,"background"
+3,5,0,111027205123000,"background"
+4,4,0,111027205123000,"background"
+5,8,0,111027205123000,"background"
+6,6,0,111027205123000,"background"
+7,7,0,111027205123000,"background"
+8,0,0,111027205123000,"background"
+9,1,1,111037187565000,"background"
+10,2,1,111037187565000,"background"
+11,3,1,111037187565000,"background"
+12,5,1,111037187565000,"background"
+13,4,1,111037187565000,"background"
+14,8,1,111037187565000,"background"
+15,6,1,111037187565000,"background"
+16,7,1,111037187565000,"background"
+17,0,1,111037187565000,"background"
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/memory_snapshot_chrome_dump_events.sql
similarity index 67%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/memory_snapshot_chrome_dump_events.sql
index cdd953a..67fbf86 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/memory_snapshot_chrome_dump_events.sql
@@ -14,12 +14,12 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
 SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  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
diff --git a/test/trace_processor/chrome/memory_snapshot_edges.out b/test/trace_processor/chrome/memory_snapshot_edges.out
new file mode 100644
index 0000000..15bb371
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_edges.out
@@ -0,0 +1,21 @@
+"id","source_node_id","target_node_id","importance"
+0,1734,1863,0
+1,1728,1839,0
+2,1732,1772,0
+3,1729,1786,0
+4,1730,1805,0
+5,1735,1859,0
+6,1733,1844,0
+7,1727,1840,0
+8,1731,1850,0
+9,1726,1849,0
+10,1636,1639,0
+11,1681,1846,0
+12,1683,1744,0
+13,1680,1849,0
+14,1682,1859,0
+15,1707,1621,0
+16,1634,1638,0
+17,1399,1193,0
+18,1083,1795,2
+19,1358,1846,0
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/memory_snapshot_edges.sql
similarity index 67%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/memory_snapshot_edges.sql
index cdd953a..63307ad 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/memory_snapshot_edges.sql
@@ -14,12 +14,10 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
 SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  id,
+  source_node_id,
+  target_node_id,
+  importance
+FROM memory_snapshot_edge
+LIMIT 20
diff --git a/test/trace_processor/chrome/memory_snapshot_general_validation.out b/test/trace_processor/chrome/memory_snapshot_general_validation.out
new file mode 100644
index 0000000..cb203ee
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_general_validation.out
@@ -0,0 +1,2 @@
+"total_snapshots","total_processes","total_process_snapshots","total_nodes","total_edges","total_node_args","total_smaps"
+2,9,18,3584,788,5014,33979
diff --git a/test/trace_processor/chrome/memory_snapshot_general_validation.sql b/test/trace_processor/chrome/memory_snapshot_general_validation.sql
new file mode 100644
index 0000000..4075041
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_general_validation.sql
@@ -0,0 +1,41 @@
+--
+-- Copyright 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
+--
+--     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
+  (
+    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
+    INNER JOIN memory_snapshot_node
+    ON args.arg_set_id = memory_snapshot_node.arg_set_id
+  ) AS total_node_args,
+  (
+    SELECT COUNT(*) FROM profiler_smaps
+    INNER JOIN memory_snapshot ON timestamp = ts
+  ) AS total_smaps
diff --git a/test/trace_processor/chrome/memory_snapshot_node_args.out b/test/trace_processor/chrome/memory_snapshot_node_args.out
new file mode 100644
index 0000000..c6adffe
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_node_args.out
@@ -0,0 +1,21 @@
+"node_id","key","value_type","int_value","string_value"
+20,"id.value","string","[NULL]","C7BC3FD59A40689345EB9CB08570641B"
+20,"locked_size.value","int",0,"[NULL]"
+20,"locked_size.unit","string","[NULL]","bytes"
+20,"virtual_size.value","int",961,"[NULL]"
+20,"virtual_size.unit","string","[NULL]","bytes"
+21,"id.value","string","[NULL]","AF621E3BE175B9B618BAADF9138E5598"
+21,"locked_size.value","int",0,"[NULL]"
+21,"locked_size.unit","string","[NULL]","bytes"
+21,"virtual_size.value","int",529,"[NULL]"
+21,"virtual_size.unit","string","[NULL]","bytes"
+30,"free_size.value","int",1048576,"[NULL]"
+30,"free_size.unit","string","[NULL]","bytes"
+32,"free_size.value","int",65472,"[NULL]"
+32,"free_size.unit","string","[NULL]","bytes"
+54,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/Site Characteristics Database"
+57,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/Sync Data/LevelDB"
+60,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/shared_proto_db/metadata"
+63,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/GCM Store/Encryption"
+66,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/Service Worker/Database"
+69,"name.value","string","[NULL]","/home/toki/.config/chromium/Default/Extension Rules"
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/memory_snapshot_node_args.sql
similarity index 67%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/memory_snapshot_node_args.sql
index cdd953a..03df9e9 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/memory_snapshot_node_args.sql
@@ -14,12 +14,12 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
 SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  node.id AS node_id,
+  key,
+  value_type,
+  int_value,
+  string_value
+FROM memory_snapshot_node node
+INNER JOIN args ON node.arg_set_id = args.arg_set_id
+LIMIT 20
diff --git a/test/trace_processor/chrome/memory_snapshot_nodes.out b/test/trace_processor/chrome/memory_snapshot_nodes.out
new file mode 100644
index 0000000..39a8108
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_nodes.out
@@ -0,0 +1,21 @@
+"id","process_snapshot_id","parent_node_id","path","size","effective_size"
+0,0,"[NULL]","cc",2899968,2899968
+1,0,0,"cc/tile_memory",2899968,2899968
+2,0,1,"cc/tile_memory/provider_0",2899968,2899968
+3,0,2,"cc/tile_memory/provider_0/resource_10",262144,262144
+4,0,2,"cc/tile_memory/provider_0/resource_11",131072,131072
+5,0,2,"cc/tile_memory/provider_0/resource_14",262144,262144
+6,0,2,"cc/tile_memory/provider_0/resource_15",131072,131072
+7,0,2,"cc/tile_memory/provider_0/resource_21",262144,262144
+8,0,2,"cc/tile_memory/provider_0/resource_23",262144,262144
+9,0,2,"cc/tile_memory/provider_0/resource_26",131072,131072
+10,0,2,"cc/tile_memory/provider_0/resource_3",262144,262144
+11,0,2,"cc/tile_memory/provider_0/resource_31",131072,131072
+12,0,2,"cc/tile_memory/provider_0/resource_36",262144,262144
+13,0,2,"cc/tile_memory/provider_0/resource_4",131072,131072
+14,0,2,"cc/tile_memory/provider_0/resource_41",16384,16384
+15,0,2,"cc/tile_memory/provider_0/resource_42",262144,262144
+16,0,2,"cc/tile_memory/provider_0/resource_7",262144,262144
+17,0,2,"cc/tile_memory/provider_0/resource_8",131072,131072
+18,0,"[NULL]","discardable",16384,16384
+19,0,18,"discardable/process_ffffffff",16384,16384
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/memory_snapshot_nodes.sql
similarity index 67%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/memory_snapshot_nodes.sql
index cdd953a..0c014d3 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/memory_snapshot_nodes.sql
@@ -14,12 +14,12 @@
 -- limitations under the License.
 --
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
 SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+  id,
+  process_snapshot_id,
+  parent_node_id,
+  path,
+  size,
+  effective_size
+FROM memory_snapshot_node
+LIMIT 20
diff --git a/test/trace_processor/chrome/memory_snapshot_os_dump_events.out b/test/trace_processor/chrome/memory_snapshot_os_dump_events.out
new file mode 100644
index 0000000..b21de21
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_os_dump_events.out
@@ -0,0 +1,19 @@
+"upid","pid","name","timestamp","detail_level","private_footprint_kb","peak_resident_set_kb","is_peak_rss_resettable"
+0,0,"[NULL]",111027205123000,"background","[NULL]","[NULL]","[NULL]"
+1,29694,"Browser",111027205123000,"background",76656640.000000,479354880.000000,1
+2,29725,"GPU Process",111027205123000,"background",62836736.000000,280952832.000000,1
+3,29728,"Service: network.mojom.NetworkService",111027205123000,"background",35360768.000000,229261312.000000,1
+4,29764,"Service: data_decoder.mojom.DataDecoderService",111027205123000,"background",30576640.000000,201347072.000000,1
+5,29737,"Service: storage.mojom.StorageService",111027205123000,"background",30617600.000000,207138816.000000,1
+6,29833,"Renderer",111027205123000,"background",36802560.000000,264036352.000000,1
+7,29877,"Service: tracing.mojom.TracingService",111027205123000,"background",31002624.000000,199208960.000000,1
+8,29807,"Renderer",111027205123000,"background",87248896.000000,451424256.000000,1
+0,0,"[NULL]",111037187565000,"background","[NULL]","[NULL]","[NULL]"
+1,29694,"Browser",111037187565000,"background",85942272.000000,491671552.000000,1
+2,29725,"GPU Process",111037187565000,"background",62754816.000000,275906560.000000,1
+3,29728,"Service: network.mojom.NetworkService",111037187565000,"background",35495936.000000,229351424.000000,1
+4,29764,"Service: data_decoder.mojom.DataDecoderService",111037187565000,"background",30679040.000000,201424896.000000,1
+5,29737,"Service: storage.mojom.StorageService",111037187565000,"background",30650368.000000,205791232.000000,1
+6,29833,"Renderer",111037187565000,"background",36950016.000000,264843264.000000,1
+7,29877,"Service: tracing.mojom.TracingService",111037187565000,"background",33386496.000000,204554240.000000,1
+8,29807,"Renderer",111037187565000,"background",76832768.000000,445292544.000000,1
diff --git a/test/trace_processor/chrome/memory_snapshot_os_dump_events.sql b/test/trace_processor/chrome/memory_snapshot_os_dump_events.sql
new file mode 100644
index 0000000..b266773
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_os_dump_events.sql
@@ -0,0 +1,42 @@
+--
+-- Copyright 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
+--
+--     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
+  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 IS '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 IS '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
diff --git a/test/trace_processor/chrome/memory_snapshot_smaps.out b/test/trace_processor/chrome/memory_snapshot_smaps.out
new file mode 100644
index 0000000..255526d
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_smaps.out
@@ -0,0 +1,21 @@
+"upid","name","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"
+1,"Browser",111027205123000,"[NULL]",4,0,0,"",14051238432768,0,"[NULL]","[NULL]",0,0,0,0,0,0
+1,"Browser",111027205123000,"[NULL]",40956,40464,0,"",14051238436864,0,"[NULL]","[NULL]",6,0,0,0,0,40464
+1,"Browser",111027205123000,"[NULL]",4,0,0,"",14051280375808,0,"[NULL]","[NULL]",0,0,0,0,0,0
+1,"Browser",111027205123000,"[NULL]",11860,8620,0,"",14051280379904,0,"[NULL]","[NULL]",6,0,0,0,0,8620
+1,"Browser",111027205123000,"[NULL]",67896,4,0,"/home/toki/chromium/src/out/Default/chrome",94168098267136,0,"[NULL]","[NULL]",4,6824,0,14812,0,8788
+1,"Browser",111027205123000,"[NULL]",123784,4,0,"/home/toki/chromium/src/out/Default/chrome",94168167792640,0,"[NULL]","[NULL]",5,62584,0,28044,0,70896
+1,"Browser",111027205123000,"[NULL]",3036,3036,0,"/home/toki/chromium/src/out/Default/chrome",94168294547456,0,"[NULL]","[NULL]",4,0,0,0,0,3036
+1,"Browser",111027205123000,"[NULL]",148,136,0,"/home/toki/chromium/src/out/Default/chrome",94168297656320,0,"[NULL]","[NULL]",6,0,0,12,0,137
+1,"Browser",111027205123000,"[NULL]",404,240,0,"",94168297807872,0,"[NULL]","[NULL]",6,0,0,0,0,240
+1,"Browser",111027205123000,"[NULL]",2048,0,0,"/dev/shm/.org.chromium.Chromium.Doo3XC (deleted)",140632555954176,0,"[NULL]","[NULL]",134,0,4,0,0,2
+1,"Browser",111027205123000,"[NULL]",2048,0,0,"/dev/shm/.org.chromium.Chromium.PxFFQm (deleted)",140632558575616,0,"[NULL]","[NULL]",134,0,4,0,0,2
+1,"Browser",111027205123000,"[NULL]",428,0,0,"/usr/lib/x86_64-linux-gnu/nss/libnssckbi.so",140632563163136,0,"[NULL]","[NULL]",5,128,0,136,0,196
+1,"Browser",111027205123000,"[NULL]",2044,0,0,"/usr/lib/x86_64-linux-gnu/nss/libnssckbi.so",140632563601408,0,"[NULL]","[NULL]",0,0,0,0,0,0
+1,"Browser",111027205123000,"[NULL]",68,68,0,"/usr/lib/x86_64-linux-gnu/nss/libnssckbi.so",140632565694464,0,"[NULL]","[NULL]",4,0,0,0,0,68
+1,"Browser",111027205123000,"[NULL]",40,40,0,"/usr/lib/x86_64-linux-gnu/nss/libnssckbi.so",140632565764096,0,"[NULL]","[NULL]",6,0,0,0,0,40
+1,"Browser",111027205123000,"[NULL]",668,0,0,"/usr/lib/x86_64-linux-gnu/nss/libfreeblpriv3.so",140632565805056,0,"[NULL]","[NULL]",5,340,0,16,0,344
+1,"Browser",111027205123000,"[NULL]",2048,0,0,"/usr/lib/x86_64-linux-gnu/nss/libfreeblpriv3.so",140632566489088,0,"[NULL]","[NULL]",0,0,0,0,0,0
+1,"Browser",111027205123000,"[NULL]",8,8,0,"/usr/lib/x86_64-linux-gnu/nss/libfreeblpriv3.so",140632568586240,0,"[NULL]","[NULL]",4,0,0,0,0,8
+1,"Browser",111027205123000,"[NULL]",4,4,0,"/usr/lib/x86_64-linux-gnu/nss/libfreeblpriv3.so",140632568594432,0,"[NULL]","[NULL]",6,0,0,0,0,4
+1,"Browser",111027205123000,"[NULL]",16,16,0,"",140632568598528,0,"[NULL]","[NULL]",6,0,0,0,0,16
diff --git a/test/trace_processor/chrome/memory_snapshot_smaps.sql b/test/trace_processor/chrome/memory_snapshot_smaps.sql
new file mode 100644
index 0000000..dfd1fce
--- /dev/null
+++ b/test/trace_processor/chrome/memory_snapshot_smaps.sql
@@ -0,0 +1,39 @@
+--
+-- Copyright 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
+--
+--     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
+  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
+INNER JOIN profiler_smaps smap ON process.upid = smap.upid
+INNER JOIN memory_snapshot ms ON ms.timestamp = smap.ts
+LIMIT 20
diff --git a/test/trace_processor/chrome/modified_rail_modes.out b/test/trace_processor/chrome/modified_rail_modes.out
new file mode 100644
index 0000000..688609b
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes.out
@@ -0,0 +1,7 @@
+
+"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"
diff --git a/test/trace_processor/chrome/modified_rail_modes.py b/test/trace_processor/chrome/modified_rail_modes.py
new file mode 100644
index 0000000..1deb786
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+from synth_common import s_to_ns
+
+trace = synth_common.create_trace()
+
+trace.add_chrome_metadata(os_name="Android")
+
+track1 = 1234
+track2 = 4567
+gpu_track = 7890
+
+trace.add_process_track_descriptor(track1, pid=0)
+trace.add_process_track_descriptor(track2, pid=2)
+trace.add_process_track_descriptor(gpu_track, pid=4)
+
+frame_period = s_to_ns(1.0 / 60)
+
+trace.add_track_event_slice("VSync", ts=s_to_ns(3), dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period, dur=10, track=gpu_track)
+# Frame skipped, but modified rail mode won't go back to foreground_idle
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 3, dur=10, track=gpu_track)
+# Larger gap now when mode will go to foreground_idle
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 12, dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 13, dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 14, dur=10, track=gpu_track)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track1, mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2),
+    track=track1,
+    mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3), dur=-1, track=track1, mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2.5),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(2.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/modified_rail_modes.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/modified_rail_modes.sql
index cdd953a..053aca8 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/modified_rail_modes.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,5 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+SELECT RUN_METRIC('chrome/rail_modes.sql') AS suppress_query_output;
+SELECT * FROM modified_rail_slices;
diff --git a/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.out b/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.out
new file mode 100644
index 0000000..70b920c
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.out
@@ -0,0 +1,5 @@
+
+"id","ts","dur","mode"
+2,0,1000000000,"response"
+3,1000000000,2500000000,"foreground_idle"
+4,3500000000,1000000000,"background"
diff --git a/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.py b/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.py
new file mode 100644
index 0000000..ff0aebc
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes_no_vsyncs.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+from synth_common import s_to_ns
+
+trace = synth_common.create_trace()
+
+trace.add_chrome_metadata(os_name="Android")
+
+track1 = 1234
+track2 = 4567
+
+trace.add_process_track_descriptor(track1, pid=0)
+trace.add_process_track_descriptor(track2, pid=2)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track1, mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2),
+    track=track1,
+    mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3), dur=-1, track=track1, mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2.5),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(2.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/chrome/modified_rail_modes_with_input.out b/test/trace_processor/chrome/modified_rail_modes_with_input.out
new file mode 100644
index 0000000..66af894
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes_with_input.out
@@ -0,0 +1,9 @@
+
+"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"
diff --git a/test/trace_processor/chrome/modified_rail_modes_with_input.py b/test/trace_processor/chrome/modified_rail_modes_with_input.py
new file mode 100644
index 0000000..5197fa2
--- /dev/null
+++ b/test/trace_processor/chrome/modified_rail_modes_with_input.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys
+
+import synth_common
+from synth_common import s_to_ns
+
+trace = synth_common.create_trace()
+
+trace.add_chrome_metadata(os_name="Android")
+
+track1 = 1234
+track2 = 4567
+gpu_track = 7890
+
+trace.add_process_track_descriptor(track1, pid=0)
+trace.add_process_track_descriptor(track2, pid=2)
+trace.add_process_track_descriptor(gpu_track, pid=4)
+
+frame_period = s_to_ns(1.0 / 60)
+
+trace.add_track_event_slice("VSync", ts=s_to_ns(3), dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period, dur=10, track=gpu_track)
+# Frame skipped, but modified rail mode won't go back to foreground_idle
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 3, dur=10, track=gpu_track)
+# Larger gap now when mode will go to foreground_idle
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 12, dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 13, dur=10, track=gpu_track)
+trace.add_track_event_slice(
+    "VSync", ts=s_to_ns(3) + frame_period * 14, dur=10, track=gpu_track)
+
+trace.add_track_event_slice(
+    "InputLatency::GestureScrollBegin", ts=s_to_ns(3), dur=10)
+trace.add_track_event_slice(
+    "InputLatency::GestureScrollEnd", ts=s_to_ns(3) + frame_period * 4, dur=10)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track1, mode=synth_common.RAIL_MODE_RESPONSE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2),
+    track=track1,
+    mode=synth_common.RAIL_MODE_LOAD)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3), dur=-1, track=track1, mode=synth_common.RAIL_MODE_IDLE)
+
+trace.add_rail_mode_slice(
+    ts=0, dur=s_to_ns(1), track=track2, mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(1),
+    dur=s_to_ns(2.5),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(2.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_ANIMATION)
+trace.add_rail_mode_slice(
+    ts=s_to_ns(3.5),
+    dur=s_to_ns(1),
+    track=track2,
+    mode=synth_common.RAIL_MODE_IDLE)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/chrome/modified_rail_modes_with_input.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/chrome/modified_rail_modes_with_input.sql
index cdd953a..053aca8 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/chrome/modified_rail_modes_with_input.sql
@@ -1,4 +1,3 @@
---
 -- Copyright 2020 The Android Open Source Project
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +11,5 @@
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
---
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+SELECT RUN_METRIC('chrome/rail_modes.sql') AS suppress_query_output;
+SELECT * FROM modified_rail_slices;
diff --git a/test/trace_processor/chrome/scroll_jank_cause.out b/test/trace_processor/chrome/scroll_jank_cause.out
index 7f7e9ed..06ced33 100644
--- a/test/trace_processor/chrome/scroll_jank_cause.out
+++ b/test/trace_processor/chrome/scroll_jank_cause.out
@@ -1,3 +1,3 @@
 
-"total","sum_explained_and_unexplained","error_rows"
-139,139,0
+"total","total_jank","sum_explained_and_unexplained","error_rows"
+139,9,9,0
diff --git a/test/trace_processor/chrome/scroll_jank_cause.sql b/test/trace_processor/chrome/scroll_jank_cause.sql
index 6c70217..658ec22 100644
--- a/test/trace_processor/chrome/scroll_jank_cause.sql
+++ b/test/trace_processor/chrome/scroll_jank_cause.sql
@@ -16,12 +16,13 @@
 
 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 NOT unexplained_jank THEN
+      CASE WHEN jank AND NOT unexplained_jank THEN
         1
       ELSE
         0
diff --git a/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.out b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.out
new file mode 100644
index 0000000..08d7501
--- /dev/null
+++ b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.out
@@ -0,0 +1,21 @@
+
+"trace_id","jank","dur_overlapping_ns","metric_name"
+2918,0,55000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.GestureProvider::OnTouchEvent"
+2918,0,433000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.RenderWidgetHostImpl::ForwardTouchEvent-PassthroughTouchEventQueue::QueueEvent-InputRouterImpl::FilterAndSendWebInputEvent-LatencyInfo.Flow"
+2918,0,66000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchNonBlockingEvent-LatencyInfo.Flow-WidgetInputHandlerManager::DidHandleInputEventSentToCompositor-MainThreadEventQueue::HandleEvent"
+2918,0,116000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchEvent-LatencyInfo.Flow-InputHandlerProxy::HandleGestureScrollBegin-WidgetInputHandlerManager::DidHandleInputEventSentToCompositor"
+2918,0,29000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchNonBlockingEvent-LatencyInfo.Flow-WidgetInputHandlerManager::DidHandleInputEventSentToCompositor-MainThreadEventQueue::HandleEvent"
+2918,0,7000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.ScrollPredictor::ResampleScrollEvents"
+2918,0,25000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.InputHandlerProxy::HandleGestureScrollUpdate-DeltaUnits"
+2918,0,6000,"InputLatency.LatencyInfo.Flow.QueuingDelay.NoJank.BlockingTasksUs.LatencyInfo.Flow"
+2926,1,52000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.InputRouterImpl::GestureEventHandled-GestureEventQueue::ProcessGestureAck"
+2926,1,17000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.GestureProvider::OnTouchEvent"
+2926,1,1208,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchNonBlockingEvent-LatencyInfo.Flow"
+2926,1,38000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchNonBlockingEvent-LatencyInfo.Flow"
+2926,1,21000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.WidgetInputHandlerImpl::DispatchEvent-LatencyInfo.Flow-EventWithCallback::CoalesceWith"
+2926,1,6000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.ScrollPredictor::ResampleScrollEvents"
+2926,1,30000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.InputHandlerProxy::HandleGestureScrollUpdate-DeltaUnits"
+2926,1,14000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.WidgetInputHandlerManager::DidHandleInputEventSentToCompositor-LatencyInfo.Flow"
+2926,1,2000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.LatencyInfo.Flow"
+2926,1,5000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.LatencyInfo.Flow"
+2926,1,8000,"InputLatency.LatencyInfo.Flow.QueuingDelay.Jank.BlockingTasksUs.LatencyInfo.Flow"
diff --git a/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.sql b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.sql
new file mode 100644
index 0000000..881308b
--- /dev/null
+++ b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay.sql
@@ -0,0 +1,25 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql')
+    AS suppress_query_output;
+
+SELECT
+  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
diff --git a/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.out b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.out
new file mode 100644
index 0000000..48fe6de
--- /dev/null
+++ b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.out
@@ -0,0 +1,3 @@
+
+"total","janky_latency_info_non_jank_avg_dur","non_janky_latency_info_non_jank_avg_dur"
+139,6387.096774,6387.096774
diff --git a/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.sql b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.sql
new file mode 100644
index 0000000..9441841
--- /dev/null
+++ b/test/trace_processor/chrome/scroll_jank_cause_queuing_delay_general_validation.sql
@@ -0,0 +1,41 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql')
+    AS suppress_query_output;
+
+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
+);
diff --git a/test/trace_processor/common/to_systrace.sql b/test/trace_processor/common/to_systrace.sql
new file mode 100644
index 0000000..b2b6ed0
--- /dev/null
+++ b/test/trace_processor/common/to_systrace.sql
@@ -0,0 +1,2 @@
+select to_ftrace(id) as line
+from raw
\ No newline at end of file
diff --git a/test/trace_processor/dynamic/connected_flow.out b/test/trace_processor/dynamic/connected_flow.out
new file mode 100644
index 0000000..8c64ff3
--- /dev/null
+++ b/test/trace_processor/dynamic/connected_flow.out
@@ -0,0 +1,49 @@
+"type","name","start_name","end_name"
+"connected","Flow0_Slice0","Flow0_Slice0","Flow0_Slice1"
+"connected","Flow0_Slice1","Flow0_Slice0","Flow0_Slice1"
+"connected","Flow1_Slice0","Flow1_Slice0","Flow1_Slice1a"
+"connected","Flow1_Slice0","Flow1_Slice0","Flow1_Slice1b"
+"connected","Flow1_Slice1a","Flow1_Slice0","Flow1_Slice1a"
+"connected","Flow1_Slice1b","Flow1_Slice0","Flow1_Slice1b"
+"connected","Flow2_Slice0","Flow2_Slice0","Flow2_Slice1"
+"connected","Flow2_Slice0","Flow2_Slice1","Flow2_Slice2"
+"connected","Flow2_Slice0","Flow2_Slice2","Flow2_Slice3a"
+"connected","Flow2_Slice0","Flow2_Slice2","Flow2_Slice3b"
+"connected","Flow2_Slice1","Flow2_Slice0","Flow2_Slice1"
+"connected","Flow2_Slice1","Flow2_Slice1","Flow2_Slice2"
+"connected","Flow2_Slice1","Flow2_Slice2","Flow2_Slice3a"
+"connected","Flow2_Slice1","Flow2_Slice2","Flow2_Slice3b"
+"connected","Flow2_Slice2","Flow2_Slice0","Flow2_Slice1"
+"connected","Flow2_Slice2","Flow2_Slice1","Flow2_Slice2"
+"connected","Flow2_Slice2","Flow2_Slice2","Flow2_Slice3a"
+"connected","Flow2_Slice2","Flow2_Slice2","Flow2_Slice3b"
+"connected","Flow2_Slice3a","Flow2_Slice0","Flow2_Slice1"
+"connected","Flow2_Slice3a","Flow2_Slice1","Flow2_Slice2"
+"connected","Flow2_Slice3a","Flow2_Slice2","Flow2_Slice3a"
+"connected","Flow2_Slice3b","Flow2_Slice0","Flow2_Slice1"
+"connected","Flow2_Slice3b","Flow2_Slice1","Flow2_Slice2"
+"connected","Flow2_Slice3b","Flow2_Slice2","Flow2_Slice3b"
+"following","Flow0_Slice0","Flow0_Slice0","Flow0_Slice1"
+"following","Flow1_Slice0","Flow1_Slice0","Flow1_Slice1a"
+"following","Flow1_Slice0","Flow1_Slice0","Flow1_Slice1b"
+"following","Flow2_Slice0","Flow2_Slice0","Flow2_Slice1"
+"following","Flow2_Slice0","Flow2_Slice1","Flow2_Slice2"
+"following","Flow2_Slice0","Flow2_Slice2","Flow2_Slice3a"
+"following","Flow2_Slice0","Flow2_Slice2","Flow2_Slice3b"
+"following","Flow2_Slice1","Flow2_Slice1","Flow2_Slice2"
+"following","Flow2_Slice1","Flow2_Slice2","Flow2_Slice3a"
+"following","Flow2_Slice1","Flow2_Slice2","Flow2_Slice3b"
+"following","Flow2_Slice2","Flow2_Slice2","Flow2_Slice3a"
+"following","Flow2_Slice2","Flow2_Slice2","Flow2_Slice3b"
+"preceding","Flow0_Slice1","Flow0_Slice0","Flow0_Slice1"
+"preceding","Flow1_Slice1a","Flow1_Slice0","Flow1_Slice1a"
+"preceding","Flow1_Slice1b","Flow1_Slice0","Flow1_Slice1b"
+"preceding","Flow2_Slice1","Flow2_Slice0","Flow2_Slice1"
+"preceding","Flow2_Slice2","Flow2_Slice0","Flow2_Slice1"
+"preceding","Flow2_Slice2","Flow2_Slice1","Flow2_Slice2"
+"preceding","Flow2_Slice3a","Flow2_Slice0","Flow2_Slice1"
+"preceding","Flow2_Slice3a","Flow2_Slice1","Flow2_Slice2"
+"preceding","Flow2_Slice3a","Flow2_Slice2","Flow2_Slice3a"
+"preceding","Flow2_Slice3b","Flow2_Slice0","Flow2_Slice1"
+"preceding","Flow2_Slice3b","Flow2_Slice1","Flow2_Slice2"
+"preceding","Flow2_Slice3b","Flow2_Slice2","Flow2_Slice3b"
diff --git a/test/trace_processor/dynamic/connected_flow.sql b/test/trace_processor/dynamic/connected_flow.sql
new file mode 100644
index 0000000..22e0692
--- /dev/null
+++ b/test/trace_processor/dynamic/connected_flow.sql
@@ -0,0 +1,30 @@
+--
+-- Copyright 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
+--
+--     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 "connected" as type, s.name, s1.name as start_name, s2.name as end_name  FROM slice s
+JOIN CONNECTED_FLOW(s.id) c
+JOIN slice s1 ON s1.id = c.slice_out
+JOIN slice s2 ON s2.id = c.slice_in
+UNION
+SELECT "following" as type, s.name, s1.name as start_name, s2.name as end_name  FROM slice s
+JOIN FOLLOWING_FLOW(s.id) c
+JOIN slice s1 ON s1.id = c.slice_out
+JOIN slice s2 ON s2.id = c.slice_in
+UNION
+SELECT "preceding" as type, s.name, s1.name as start_name, s2.name as end_name  FROM slice s
+JOIN PRECEDING_FLOW(s.id) c
+JOIN slice s1 ON s1.id = c.slice_out
+JOIN slice s2 ON s2.id = c.slice_in
+ORDER BY type, s.name, s1.name, s2.name ASC
diff --git a/test/trace_processor/dynamic/connected_flow_data.json b/test/trace_processor/dynamic/connected_flow_data.json
new file mode 100644
index 0000000..ed748bc
--- /dev/null
+++ b/test/trace_processor/dynamic/connected_flow_data.json
@@ -0,0 +1,128 @@
+{
+    "traceEvents": [
+      {"args":{"name":"MainProcess"},"cat":"__metadata","name":"process_name","ph":"M","pid":100,"tid":0,"ts":0},
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15903,
+        "ts": 420,
+        "ph": "X",
+        "name": "Flow0_Slice0",
+        "args":{},
+        "dur": 150,
+        "bind_id": "0x401",
+        "flow_out": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15895,
+        "ts": 720,
+        "ph": "X",
+        "name": "Flow0_Slice1",
+        "args":{},
+        "dur": 200,
+        "bind_id": "0x401",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15904,
+        "ts": 400,
+        "ph": "X",
+        "name": "Flow1_Slice0",
+        "args":{},
+        "dur": 600,
+        "bind_id": "0x403",
+        "flow_out": true,
+        "flow_in": false
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15903,
+        "ts": 1300,
+        "ph": "X",
+        "name": "Flow1_Slice1a",
+        "args":{},
+        "dur": 600,
+        "bind_id": "0x403",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15794,
+        "ts": 1100,
+        "ph": "X",
+        "name": "Flow1_Slice1b",
+        "args":{},
+        "dur": 900,
+        "bind_id": "0x403",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15903,
+        "ts": 1351,
+        "ph": "X",
+        "name": "Flow2_Slice0",
+        "args": {},
+        "dur": 500,
+        "bind_id": "0x402",
+        "flow_out": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15903,
+        "ts": 2002,
+        "ph": "X",
+        "name": "Flow2_Slice1",
+        "args":{},
+        "dur": 350,
+        "bind_id": "0x402",
+        "flow_out": true,
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15794,
+        "ts": 2400,
+        "ph": "X",
+        "name": "Flow2_Slice2",
+        "args": {},
+        "dur": 300,
+        "bind_id": "0x402",
+        "flow_in": true,
+        "flow_out": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15904,
+        "ts": 3031,
+        "ph": "X",
+        "name": "Flow2_Slice3a",
+        "args": {},
+        "dur": 400,
+        "bind_id": "0x402",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 100,
+        "tid": 15895,
+        "ts": 3333,
+        "ph": "X",
+        "name": "Flow2_Slice3b",
+        "args": {},
+        "dur": 400,
+        "bind_id": "0x402",
+        "flow_in": true
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/test/trace_processor/dynamic/index b/test/trace_processor/dynamic/index
index 10f8253..8e76690 100644
--- a/test/trace_processor/dynamic/index
+++ b/test/trace_processor/dynamic/index
@@ -4,4 +4,7 @@
 relationship_tables.textproto ancestor_slice.sql ancestor_slice.out
 
 # Descendant slice table.
-relationship_tables.textproto descendant_slice.sql descendant_slice.out
\ No newline at end of file
+relationship_tables.textproto descendant_slice.sql descendant_slice.out
+
+# Connected/Following/Perceeding flow table.
+connected_flow_data.json connected_flow.sql connected_flow.out
\ No newline at end of file
diff --git a/test/trace_processor/fuchsia/fuchsia_smoke_flow.out b/test/trace_processor/fuchsia/fuchsia_smoke_flow.out
new file mode 100644
index 0000000..6726a35
--- /dev/null
+++ b/test/trace_processor/fuchsia/fuchsia_smoke_flow.out
@@ -0,0 +1,11 @@
+"id","slice_out","slice_in"
+0,0,1
+1,2,3
+2,4,5
+3,6,7
+4,8,9
+5,10,11
+6,12,13
+7,14,15
+8,16,17
+9,18,19
diff --git a/test/trace_processor/fuchsia/fuchsia_workstation_smoke_args.out b/test/trace_processor/fuchsia/fuchsia_workstation_smoke_args.out
new file mode 100644
index 0000000..3b83654
--- /dev/null
+++ b/test/trace_processor/fuchsia/fuchsia_workstation_smoke_args.out
@@ -0,0 +1,11 @@
+"key","COUNT(*)"
+"Dart Arguments",3
+"Escher frame number",33
+"Expected presentation time",17
+"Frame number",33
+"MinikinFontsCount",2
+"Predicted frame duration(ms)",21
+"Render time(ms)",21
+"Timestamp",917
+"Update time(ms)",21
+"Vsync interval",900
diff --git a/test/trace_processor/fuchsia/fuchsia_workstation_smoke_slices.out b/test/trace_processor/fuchsia/fuchsia_workstation_smoke_slices.out
index 2d133e8..daab85f 100644
--- a/test/trace_processor/fuchsia/fuchsia_workstation_smoke_slices.out
+++ b/test/trace_processor/fuchsia/fuchsia_workstation_smoke_slices.out
@@ -1,4 +1,7 @@
 "type","depth","count"
+"process_track",0,85
+"process_track",1,14
+"process_track",2,1
 "thread_track",0,13379
 "thread_track",1,11548
 "thread_track",2,10181
@@ -19,6 +22,3 @@
 "thread_track",17,38
 "thread_track",18,12
 "thread_track",19,1
-"track",0,85
-"track",1,14
-"track",2,1
diff --git a/test/trace_processor/fuchsia/index b/test/trace_processor/fuchsia/index
index 538f588..bb89b30 100644
--- a/test/trace_processor/fuchsia/index
+++ b/test/trace_processor/fuchsia/index
@@ -5,6 +5,8 @@
 ../../data/fuchsia_trace.fxt ../common/smoke_slices.sql fuchsia_smoke_slices.out
 ../../data/fuchsia_trace.fxt smoke_instants.sql fuchsia_smoke_instants.out
 ../../data/fuchsia_trace.fxt smoke_counters.sql fuchsia_smoke_counters.out
+../../data/fuchsia_trace.fxt smoke_flow.sql fuchsia_smoke_flow.out
 
 # Smoke test a high-CPU trace.
 ../../data/fuchsia_workstation.fxt ../common/smoke_slices.sql fuchsia_workstation_smoke_slices.out
+../../data/fuchsia_workstation.fxt smoke_args.sql fuchsia_workstation_smoke_args.out
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/fuchsia/smoke_args.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/fuchsia/smoke_args.sql
index cdd953a..522f538 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/fuchsia/smoke_args.sql
@@ -13,13 +13,9 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+select
+  key,
+  COUNT(*)
+from args
+group by key
+limit 10;
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/fuchsia/smoke_flow.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/fuchsia/smoke_flow.sql
index cdd953a..e0b8af4 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/fuchsia/smoke_flow.sql
@@ -13,13 +13,9 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+select
+  id,
+  slice_out,
+  slice_in
+from flow
+limit 10;
diff --git a/test/trace_processor/graphics/android_sysui_cuj.out b/test/trace_processor/graphics/android_sysui_cuj.out
new file mode 100644
index 0000000..52933ca
--- /dev/null
+++ b/test/trace_processor/graphics/android_sysui_cuj.out
@@ -0,0 +1,44 @@
+android_sysui_cuj {
+ frames {
+   number: 1
+   ts: 0
+   dur: 15000000
+ }
+ frames {
+   number: 2
+   ts: 8000000
+   dur: 27000000
+   jank_cause: "MainThread - binder transaction time"
+ }
+ frames {
+   number: 3
+   ts: 30000000
+   dur: 22000000
+   jank_cause: "RenderThread - long flush layers"
+   jank_cause: "MainThread - binder calls count"
+ }
+ frames {
+   number: 4
+   ts: 40000000
+   dur: 38000000
+   jank_cause: "GPU completion - long completion time"
+   jank_cause: "Long running time"
+ }
+ frames {
+   number: 5
+   ts: 70000000
+   dur: 18000000
+   jank_cause: "RenderThread - scheduler"
+ }
+  frames {
+    number: 6
+    ts: 100000000
+    dur: 22000000
+   jank_cause: "GPU completion - long completion time"
+  }
+  frames {
+    number: 7
+    ts: 200000000
+    dur: 10000000
+  }
+}
\ No newline at end of file
diff --git a/test/trace_processor/graphics/android_sysui_cuj.py b/test/trace_processor/graphics/android_sysui_cuj.py
new file mode 100644
index 0000000..44761aa
--- /dev/null
+++ b/test/trace_processor/graphics/android_sysui_cuj.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys, path
+
+import synth_common
+
+PID = 1000
+RTID = 1555
+
+
+def add_main_thread_atrace(trace, ts, ts_end, buf):
+  trace.add_atrace_begin(ts=ts, tid=PID, pid=PID, buf=buf)
+  trace.add_atrace_end(ts=ts_end, tid=PID, pid=PID)
+
+
+def add_render_thread_atrace(trace, ts, ts_end, buf):
+  trace.add_atrace_begin(ts=ts, tid=RTID, pid=PID, buf=buf)
+  trace.add_atrace_end(ts=ts_end, tid=RTID, pid=PID)
+
+
+def add_gpu_thread_atrace(trace, ts, ts_end, buf):
+  trace.add_atrace_begin(ts=ts, tid=1666, pid=PID, buf=buf)
+  trace.add_atrace_end(ts=ts_end, tid=1666, pid=PID)
+
+
+def add_frame(trace, ts_do_frame, ts_end_do_frame, ts_draw_frame,
+              ts_end_draw_frame, ts_gpu, ts_end_gpu):
+  add_main_thread_atrace(trace, ts_do_frame, ts_end_do_frame,
+                         "Choreographer#doFrame")
+  add_render_thread_atrace(trace, ts_draw_frame, ts_end_draw_frame, "DrawFrame")
+  add_gpu_thread_atrace(trace, ts_gpu, ts_end_gpu,
+                        "waiting for GPU completion 123")
+
+
+trace = synth_common.create_trace()
+
+trace.add_packet()
+trace.add_package_list(
+    ts=0, name="com.android.systemui", uid=10001, version_code=1)
+
+trace.add_process(pid=PID, ppid=1, cmdline="com.android.systemui", uid=10001)
+trace.add_thread(
+    tid=RTID, tgid=PID, cmdline="RenderThread", name="RenderThread")
+trace.add_thread(
+    tid=1666, tgid=PID, cmdline="GPU completion", name="GPU completion")
+
+trace.add_ftrace_packet(cpu=0)
+trace.add_atrace_async_begin(ts=0, tid=PID, pid=PID, buf="Cuj<5>")
+trace.add_atrace_async_end(ts=1_000_000_000, tid=PID, pid=PID, buf="Cuj<5>")
+
+add_frame(
+    trace,
+    ts_do_frame=0,
+    ts_end_do_frame=5_000_000,
+    ts_draw_frame=4_000_000,
+    ts_end_draw_frame=5_000_000,
+    ts_gpu=10_000_000,
+    ts_end_gpu=15_000_000)
+add_main_thread_atrace(
+    trace, ts=1_500_000, ts_end=2_000_000, buf="binder transaction")
+add_render_thread_atrace(
+    trace, ts=4_500_000, ts_end=4_800_000, buf="flush layers")
+
+add_frame(
+    trace,
+    ts_do_frame=8_000_000,
+    ts_end_do_frame=23_000_000,
+    ts_draw_frame=22_000_000,
+    ts_end_draw_frame=26_000_000,
+    ts_gpu=27_500_000,
+    ts_end_gpu=35_000_000)
+add_main_thread_atrace(
+    trace, ts=9_000_000, ts_end=20_000_000, buf="binder transaction")
+add_render_thread_atrace(
+    trace, ts=24_000_000, ts_end=25_000_000, buf="flush layers")
+
+add_frame(
+    trace,
+    ts_do_frame=30_000_000,
+    ts_end_do_frame=33_000_000,
+    ts_draw_frame=31_000_000,
+    ts_end_draw_frame=50_000_000,
+    ts_gpu=51_500_000,
+    ts_end_gpu=52_000_000)
+add_main_thread_atrace(
+    trace, ts=31_000_000, ts_end=31_050_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_100_000, ts_end=31_150_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_200_000, ts_end=31_250_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_300_000, ts_end=31_350_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_400_000, ts_end=31_450_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_500_000, ts_end=31_550_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_600_000, ts_end=31_650_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_700_000, ts_end=31_750_000, buf="binder transaction")
+add_main_thread_atrace(
+    trace, ts=31_800_000, ts_end=31_850_000, buf="binder transaction")
+add_render_thread_atrace(
+    trace, ts=38_000_000, ts_end=50_000_000, buf="flush layers")
+
+add_frame(
+    trace,
+    ts_do_frame=40_000_000,
+    ts_end_do_frame=53_000_000,
+    ts_draw_frame=52_000_000,
+    ts_end_draw_frame=59_000_000,
+    ts_gpu=66_500_000,
+    ts_end_gpu=78_000_000)
+
+# Main thread Running for 14 millis
+trace.add_sched(ts=39_000_000, prev_pid=0, next_pid=PID)
+trace.add_sched(ts=53_000_000, prev_pid=PID, next_pid=0, prev_state='R')
+
+# RenderThread Running for 5 millis
+trace.add_sched(ts=54_000_000, prev_pid=0, next_pid=RTID)
+trace.add_sched(ts=59_000_000, prev_pid=RTID, next_pid=0, prev_state='R')
+
+add_frame(
+    trace,
+    ts_do_frame=70_000_000,
+    ts_end_do_frame=80_000_000,
+    ts_draw_frame=78_000_000,
+    ts_end_draw_frame=87_000_000,
+    ts_gpu=86_500_000,
+    ts_end_gpu=88_000_000)
+
+# Main thread Running for 1 millis
+trace.add_sched(ts=70_000_000, prev_pid=0, next_pid=PID)
+trace.add_sched(ts=71_000_000, prev_pid=PID, next_pid=0, prev_state='R')
+
+# RenderThread Running for 1 millis and R for 9.5 millis
+trace.add_sched(ts=78_000_000, prev_pid=0, next_pid=RTID)
+trace.add_sched(ts=78_500_000, prev_pid=RTID, next_pid=0, prev_state='R')
+trace.add_sched(ts=78_500_000, prev_pid=0, next_pid=0)
+trace.add_sched(ts=88_000_000, prev_pid=0, next_pid=RTID)
+trace.add_sched(ts=88_500_000, prev_pid=RTID, next_pid=0, prev_state='R')
+
+add_frame(
+    trace,
+    ts_do_frame=100_000_000,
+    ts_end_do_frame=115_000_000,
+    ts_draw_frame=102_000_000,
+    ts_end_draw_frame=104_000_000,
+    ts_gpu=108_000_000,
+    ts_end_gpu=115_600_000)
+
+add_render_thread_atrace(
+    trace, ts=108_000_000, ts_end=114_000_000, buf="DrawFrame")
+add_gpu_thread_atrace(
+    trace,
+    ts=121_500_000,
+    ts_end=122_000_000,
+    buf="waiting for GPU completion 123")
+
+add_frame(
+    trace,
+    ts_do_frame=200_000_000,
+    ts_end_do_frame=215_000_000,
+    ts_draw_frame=202_000_000,
+    ts_end_draw_frame=204_000_000,
+    ts_gpu=208_000_000,
+    ts_end_gpu=210_000_000)
+
+add_render_thread_atrace(
+    trace, ts=208_000_000, ts_end=214_000_000, buf="DrawFrame")
+
+add_frame(
+    trace,
+    ts_do_frame=300_000_000,
+    ts_end_do_frame=315_000_000,
+    ts_draw_frame=302_000_000,
+    ts_end_draw_frame=304_000_000,
+    ts_gpu=308_000_000,
+    ts_end_gpu=310_000_000)
+
+add_render_thread_atrace(
+    trace, ts=305_000_000, ts_end=308_000_000, buf="dispatchFrameCallbacks")
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/gpu_mem_total.out b/test/trace_processor/graphics/gpu_mem_total.out
index a62760d..e39c9a0 100644
--- a/test/trace_processor/graphics/gpu_mem_total.out
+++ b/test/trace_processor/graphics/gpu_mem_total.out
@@ -1,7 +1,7 @@
-"name","ts","pid","value"
-"gpumemtotal.global",0,"[NULL]",123
-"gpumemtotal.process",0,1,100
-"gpumemtotal.global",5,"[NULL]",256
-"gpumemtotal.process",5,1,233
-"gpumemtotal.global",10,"[NULL]",123
-"gpumemtotal.process",10,1,0
+"name","unit","description","ts","pid","value"
+"GPU Memory","7","Total GPU memory used by the entire system",0,"[NULL]",123
+"GPU Memory","7","Total GPU memory used by this process",0,1,100
+"GPU Memory","7","Total GPU memory used by the entire system",5,"[NULL]",256
+"GPU Memory","7","Total GPU memory used by this process",5,1,233
+"GPU Memory","7","Total GPU memory used by the entire system",10,"[NULL]",123
+"GPU Memory","7","Total GPU memory used by this process",10,1,0
diff --git a/test/trace_processor/graphics/gpu_mem_total.py b/test/trace_processor/graphics/gpu_mem_total.py
index bb9757b..e21d8a8 100755
--- a/test/trace_processor/graphics/gpu_mem_total.py
+++ b/test/trace_processor/graphics/gpu_mem_total.py
@@ -19,19 +19,21 @@
 
 trace = synth_common.create_trace()
 trace.add_packet()
-trace.add_process(0, 0, "global")
-trace.add_process(1, 0, "app_1")
 
-# Global gpu_mem_total event
+# Global gpu_mem_total initial counter event
+trace.add_gpu_mem_total_event(pid=0, ts=0, size=123)
+
+# Global gpu_mem_total ftrace event
 trace.add_ftrace_packet(cpu=0)
-trace.add_gpu_mem_total(pid=0, ts=0, size=123)
-trace.add_gpu_mem_total(pid=0, ts=5, size=256)
-trace.add_gpu_mem_total(pid=0, ts=10, size=123)
+trace.add_gpu_mem_total_ftrace_event(pid=0, ts=5, size=256)
+trace.add_gpu_mem_total_ftrace_event(pid=0, ts=10, size=123)
 
-# pid = 1
+# gpu_mem_total initial counter event for pid = 1
+trace.add_gpu_mem_total_event(pid=1, ts=0, size=100)
+
+# gpu_mem_total ftrace event for pid = 1
 trace.add_ftrace_packet(cpu=1)
-trace.add_gpu_mem_total(pid=1, ts=0, size=100)
-trace.add_gpu_mem_total(pid=1, ts=5, size=233)
-trace.add_gpu_mem_total(pid=1, ts=10, size=0)
+trace.add_gpu_mem_total_ftrace_event(pid=1, ts=5, size=233)
+trace.add_gpu_mem_total_ftrace_event(pid=1, ts=10, size=0)
 
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/gpu_mem_total.sql b/test/trace_processor/graphics/gpu_mem_total.sql
index 60b2f79..be985aa 100644
--- a/test/trace_processor/graphics/gpu_mem_total.sql
+++ b/test/trace_processor/graphics/gpu_mem_total.sql
@@ -1,4 +1,4 @@
-SELECT ct.name, c.ts, p.pid, CAST(c.value as INT) as value
+SELECT ct.name, ct.unit, ct.description, c.ts, p.pid, CAST(c.value as INT) as value
 FROM counter_track ct
 LEFT JOIN process_counter_track pct USING (id)
 LEFT JOIN process p USING (upid)
diff --git a/test/trace_processor/graphics/gpu_metric.out b/test/trace_processor/graphics/gpu_metric.out
new file mode 100644
index 0000000..d0ca395
--- /dev/null
+++ b/test/trace_processor/graphics/gpu_metric.out
@@ -0,0 +1,17 @@
+android_gpu {
+  processes {
+    name: "app_1"
+    mem_max: 8
+    mem_min: 2
+    mem_avg: 3
+  }
+  processes {
+    name: "app_2"
+    mem_max: 10
+    mem_min: 6
+    mem_avg: 8
+  }
+  mem_max: 4
+  mem_min: 1
+  mem_avg: 2
+}
diff --git a/test/trace_processor/graphics/gpu_metric.py b/test/trace_processor/graphics/gpu_metric.py
new file mode 100644
index 0000000..8179d08
--- /dev/null
+++ b/test/trace_processor/graphics/gpu_metric.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys, path
+
+import synth_common
+
+trace = synth_common.create_trace()
+trace.add_packet()
+trace.add_process(1, 0, "app_1")
+trace.add_process(2, 0, "app_2")
+trace.add_process(3, 0, "app_2")
+
+trace.add_ftrace_packet(cpu=0)
+
+# max=4, min=1, avg=(2*1+5*2+(10-9)*4)/(10-2)=2
+trace.add_gpu_mem_total_ftrace_event(pid=0, ts=2, size=1)
+trace.add_gpu_mem_total_ftrace_event(pid=0, ts=4, size=2)
+trace.add_gpu_mem_total_ftrace_event(pid=0, ts=9, size=4)
+
+# max=8, min=2, avg=(5*2+(10-9)*8)/(10-4)=3
+trace.add_gpu_mem_total_ftrace_event(pid=1, ts=4, size=2)
+trace.add_gpu_mem_total_ftrace_event(pid=1, ts=9, size=8)
+
+# max=8, min=6, avgxdur=2*6+(10-4)*8=60, dur=2+(10-4)=8
+trace.add_gpu_mem_total_ftrace_event(pid=2, ts=2, size=6)
+trace.add_gpu_mem_total_ftrace_event(pid=2, ts=4, size=8)
+
+# max=10, min=7, avgxdur=1*7+(10-7)*10=37, dur=1+(10-7)=4
+trace.add_gpu_mem_total_ftrace_event(pid=3, ts=6, size=7)
+trace.add_gpu_mem_total_ftrace_event(pid=3, ts=7, size=10)
+
+# app_2 will be aggregated
+# max=10, min=6, avg=(60+37)/(8+4)=8
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/graphics_frame_events.out b/test/trace_processor/graphics/graphics_frame_events.out
index 8aca540..59dce99 100644
--- a/test/trace_processor/graphics/graphics_frame_events.out
+++ b/test/trace_processor/graphics/graphics_frame_events.out
@@ -19,3 +19,24 @@
 16,"Display_layer2",-1,"12",12,"layer2"
 24,"Buffer: 1",0,"PresentFenceSignaled",13,"layer1"
 24,"Display_layer1",-1,"13",13,"layer1"
+31,"Buffer: 1",0,"Dequeue",21,"layer1"
+31,"APP_1",3,"21",21,"layer1"
+34,"Buffer: 1",0,"Queue",21,"layer1"
+34,"GPU_1",-1,"21",21,"layer1"
+37,"Buffer: 1",0,"Dequeue",22,"layer1"
+37,"APP_1",4,"22",22,"layer1"
+41,"Buffer: 1",0,"Queue",22,"layer1"
+41,"GPU_1",5,"22",22,"layer1"
+46,"Buffer: 1",0,"AcquireFenceSignaled",22,"layer1"
+53,"Buffer: 2",0,"Dequeue",24,"layer2"
+53,"APP_2",-1,"0",0,"layer2"
+59,"Buffer: 2",0,"AcquireFenceSignaled",24,"layer2"
+61,"Buffer: 2",0,"Latch",24,"layer2"
+61,"SF_2",-1,"24",24,"layer2"
+63,"Buffer: 1",0,"Dequeue",25,"layer1"
+63,"APP_1",-1,"0",0,"layer1"
+73,"Buffer: 1",0,"Dequeue",26,"layer1"
+73,"APP_1",2,"26",26,"layer1"
+75,"Buffer: 1",0,"Queue",26,"layer1"
+75,"GPU_1",4,"26",26,"layer1"
+79,"Buffer: 1",0,"AcquireFenceSignaled",26,"layer1"
diff --git a/test/trace_processor/graphics/graphics_frame_events.py b/test/trace_processor/graphics/graphics_frame_events.py
index 2366ad2..bf28f1f 100755
--- a/test/trace_processor/graphics/graphics_frame_events.py
+++ b/test/trace_processor/graphics/graphics_frame_events.py
@@ -52,4 +52,19 @@
 trace.add_buffer_event_packet(ts=6, buffer_id=-1, layer_name="layer6", frame_number=14, event_type=BufferEvent.HWC_COMPOSITION_QUEUED, duration=0)
 # Missing type.
 trace.add_buffer_event_packet(ts=7, buffer_id=7, layer_name="layer7", frame_number=15, event_type=-1, duration=0)
+# Missing Acquire
+trace.add_buffer_event_packet(ts=31, buffer_id=1, layer_name="layer1", frame_number=21, event_type=BufferEvent.DEQUEUE, duration=0)
+trace.add_buffer_event_packet(ts=34, buffer_id=1, layer_name="layer1", frame_number=21, event_type=BufferEvent.QUEUE, duration=0)
+trace.add_buffer_event_packet(ts=37, buffer_id=1, layer_name="layer1", frame_number=22, event_type=BufferEvent.DEQUEUE, duration=0)
+trace.add_buffer_event_packet(ts=41, buffer_id=1, layer_name="layer1", frame_number=22, event_type=BufferEvent.QUEUE, duration=0)
+trace.add_buffer_event_packet(ts=46, buffer_id=1, layer_name="layer1", frame_number=22, event_type=BufferEvent.ACQUIRE_FENCE, duration=0)
+# Missing queue with acquire
+trace.add_buffer_event_packet(ts=53, buffer_id=2, layer_name="layer2", frame_number=24, event_type=BufferEvent.DEQUEUE, duration=0)
+trace.add_buffer_event_packet(ts=59, buffer_id=2, layer_name="layer2", frame_number=24, event_type=BufferEvent.ACQUIRE_FENCE, duration=0)
+trace.add_buffer_event_packet(ts=61, buffer_id=2, layer_name="layer2", frame_number=24, event_type=BufferEvent.LATCH, duration=0)
+# Missing queue without acquire
+trace.add_buffer_event_packet(ts=63, buffer_id=1, layer_name="layer1", frame_number=25, event_type=BufferEvent.DEQUEUE, duration=0)
+trace.add_buffer_event_packet(ts=73, buffer_id=1, layer_name="layer1", frame_number=26, event_type=BufferEvent.DEQUEUE, duration=0)
+trace.add_buffer_event_packet(ts=75, buffer_id=1, layer_name="layer1", frame_number=26, event_type=BufferEvent.QUEUE, duration=0)
+trace.add_buffer_event_packet(ts=79, buffer_id=1, layer_name="layer1", frame_number=26, event_type=BufferEvent.ACQUIRE_FENCE, duration=0)
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/graphics/index b/test/trace_processor/graphics/index
index 93c6093..f830cd9 100644
--- a/test/trace_processor/graphics/index
+++ b/test/trace_processor/graphics/index
@@ -19,3 +19,9 @@
 
 # Missed frames
 frame_missed.py frame_missed_event.sql frame_missed_event_frame_missed.out
+
+# GPU metrics
+gpu_metric.py android_gpu gpu_metric.out
+
+# Android SysUI CUJs metrics
+android_sysui_cuj.py android_sysui_cuj android_sysui_cuj.out
diff --git a/test/trace_processor/parsing/android_thread_time_in_state.out b/test/trace_processor/parsing/android_thread_time_in_state.out
index 1b146f1..e02191d 100644
--- a/test/trace_processor/parsing/android_thread_time_in_state.out
+++ b/test/trace_processor/parsing/android_thread_time_in_state.out
@@ -4,14 +4,14 @@
       name: "com.google.pid5"
     }
     metrics_by_core_type {
-      core_type: "unknown"
+      core_type: "little"
       runtime_ms: 20
       mcycles: 3
     }
     threads {
       main_thread: true
       metrics_by_core_type {
-        core_type: "unknown"
+        core_type: "little"
         runtime_ms: 20
         mcycles: 3
       }
@@ -22,7 +22,7 @@
       name: "com.google.pid11"
     }
     metrics_by_core_type {
-      core_type: "unknown"
+      core_type: "little"
       runtime_ms: 20
       mcycles: 40
     }
@@ -30,7 +30,7 @@
       name: "tid11"
       main_thread: true
       metrics_by_core_type {
-        core_type: "unknown"
+        core_type: "little"
         runtime_ms: 10
         mcycles: 20
       }
@@ -39,7 +39,7 @@
       name: "tid12"
       main_thread: false
       metrics_by_core_type {
-        core_type: "unknown"
+        core_type: "little"
         runtime_ms: 10
         mcycles: 20
       }
@@ -50,14 +50,14 @@
       name: "com.google.pid17"
     }
     metrics_by_core_type {
-      core_type: "unknown"
+      core_type: "little"
       runtime_ms: 10
       mcycles: 1
     }
     threads {
       main_thread: true
       metrics_by_core_type {
-        core_type: "unknown"
+        core_type: "little"
         runtime_ms: 10
         mcycles: 1
       }
diff --git a/test/trace_processor/parsing/config_metadata.out b/test/trace_processor/parsing/config_metadata.out
index edcba50..3a77f45 100644
--- a/test/trace_processor/parsing/config_metadata.out
+++ b/test/trace_processor/parsing/config_metadata.out
@@ -1,3 +1,5 @@
 "name","str_value"
 "android_build_fingerprint","the fingerprint"
+"trace_config_pbtxt","trace_uuid_msb: 1314564453825188563
+trace_uuid_lsb: -6605018796207623390"
 "trace_uuid","123e4567-e89b-12d3-a456-426655443322"
diff --git a/test/trace_processor/parsing/display_time_unit_slices.out b/test/trace_processor/parsing/display_time_unit_slices.out
new file mode 100644
index 0000000..4343a9e
--- /dev/null
+++ b/test/trace_processor/parsing/display_time_unit_slices.out
@@ -0,0 +1,2 @@
+"ts","dur","name"
+1597071955492308000,211463000,"add_graph"
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/parsing/flow_events.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/parsing/flow_events.sql
index cdd953a..11ef1a9 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/parsing/flow_events.sql
@@ -13,13 +13,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+select t1.name as slice_out, t2.name as slice_in from flow t
+join slice t1 on t.slice_out == t1.slice_id
+join slice t2 on t.slice_in == t2.slice_id;
diff --git a/test/trace_processor/parsing/flow_events_json_v1.json b/test/trace_processor/parsing/flow_events_json_v1.json
new file mode 100644
index 0000000..461aa39
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_json_v1.json
@@ -0,0 +1,123 @@
+{
+    "traceEvents": [
+      {
+        "cat": "ipc",
+        "pid": 15902,
+        "tid": 15903,
+        "ts": 3002,
+        "ph": "X",
+        "name": "SenderA",
+        "args":{},
+        "dur": 10
+      },
+      {
+        "cat": "ipc",
+        "pid": 15902,
+        "tid": 15903,
+        "ts": 3002,
+        "ph": "s",
+        "name": "IPC",
+        "args":{},
+        "id":"0x402",
+        "sf": "7"
+      },
+      {
+        "cat": "ipc",
+        "pid": 15903,
+        "tid": 15904,
+        "ts": 0,
+        "ph": "X",
+        "name": "SenderB",
+        "args":{},
+        "dur": 10
+      },
+      {
+        "cat": "ipc",
+        "pid": 15903,
+        "tid": 15904,
+        "ts": 1,
+        "ph": "s",
+        "name": "IPC",
+        "args":{},
+        "id":"0x403"
+      },
+      {
+        "cat": "ipc",
+        "pid": 15875,
+        "tid": 15895,
+        "ts": 1001,
+        "ph": "f",
+        "name": "IPC",
+        "args": {},
+        "id": "0x403"
+      },
+      {
+        "cat": "ipc",
+        "pid": 15875,
+        "tid": 15895,
+        "ts": 1001,
+        "ph": "X",
+        "name": "Blergh",
+        "args": {},
+        "dur": 100
+      },
+      {
+        "cat": "ipc",
+        "pid": 15774,
+        "tid": 15794,
+        "ts": 3200,
+        "ph": "X",
+        "name": "OtherSlice",
+        "args": {},
+        "dur": 500
+      },
+      {
+        "cat": "ipc",
+        "pid": 15774,
+        "tid": 15794,
+        "ts": 3220,
+        "ph": "t",
+        "name": "IPC",
+        "args": {},
+        "id": "0x402",
+        "sf": 8
+      },
+      {
+        "cat": "ipc",
+        "pid": 15874,
+        "tid": 15894,
+        "ts": 3330,
+        "ph": "f",
+        "name": "IPC",
+        "args": {},
+        "id": "0x402",
+        "sf": "1"
+      },
+      {
+        "cat": "ipc",
+        "pid": 15874,
+        "tid": 15894,
+        "ts": 3331,
+        "ph": "X",
+        "name": "SomeSlice",
+        "args": {},
+        "dur": 400
+      }
+    ],
+    "stackFrames": {
+      "1": {
+        "category": "m1",
+        "name": "main"
+      },
+      "7": {
+        "category": "m2",
+        "name": "frame7",
+        "parent": "1"
+      },
+      "8": {
+        "category": "m2",
+        "name": "frame8",
+        "parent": "1"
+      }
+    }
+  }
\ No newline at end of file
diff --git a/test/trace_processor/parsing/flow_events_json_v1.out b/test/trace_processor/parsing/flow_events_json_v1.out
new file mode 100644
index 0000000..3cef532
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_json_v1.out
@@ -0,0 +1,4 @@
+"slice_out","slice_in"
+"SenderB","Blergh"
+"SenderA","OtherSlice"
+"OtherSlice","SomeSlice"
diff --git a/test/trace_processor/parsing/flow_events_json_v2.json b/test/trace_processor/parsing/flow_events_json_v2.json
new file mode 100644
index 0000000..f62e86d
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_json_v2.json
@@ -0,0 +1,86 @@
+{
+    "traceEvents": [
+      {
+        "cat": "ipc",
+        "pid": 15902,
+        "tid": 15903,
+        "ts": 3002,
+        "ph": "X",
+        "name": "SenderA",
+        "args":{},
+        "dur": 10,
+        "bind_id": "0x402",
+        "flow_out": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 15903,
+        "tid": 15904,
+        "ts": 0,
+        "ph": "X",
+        "name": "SenderB",
+        "args":{},
+        "dur": 10,
+        "bind_id": "0x403",
+        "flow_out": true,
+        "flow_in": false
+      },
+      {
+        "cat": "ipc",
+        "pid": 15875,
+        "tid": 15895,
+        "ts": 1001,
+        "ph": "X",
+        "name": "Blergh",
+        "args": {},
+        "dur": 100,
+        "bind_id": "0x403",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 15774,
+        "tid": 15794,
+        "ts": 3200,
+        "ph": "X",
+        "name": "OtherSlice",
+        "args": {},
+        "dur": 500,
+        "bind_id": "0x402",
+        "flow_in": true,
+        "flow_out": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 15874,
+        "tid": 15894,
+        "ts": 3331,
+        "ph": "X",
+        "name": "SomeSlice",
+        "args": {},
+        "dur": 400,
+        "bind_id": "0x402",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 15875,
+        "tid": 15895,
+        "ts": 3333,
+        "ph": "B",
+        "name": "SomeOtherSlice",
+        "args": {},
+        "bind_id": "0x402",
+        "flow_in": true
+      },
+      {
+        "cat": "ipc",
+        "pid": 15875,
+        "tid": 15895,
+        "ts": 4330,
+        "ph": "E",
+        "name": "SomeOtherSlice",
+        "args": {}
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/test/trace_processor/parsing/flow_events_json_v2.out b/test/trace_processor/parsing/flow_events_json_v2.out
new file mode 100644
index 0000000..3090047
--- /dev/null
+++ b/test/trace_processor/parsing/flow_events_json_v2.out
@@ -0,0 +1,5 @@
+"slice_out","slice_in"
+"SenderB","Blergh"
+"SenderA","OtherSlice"
+"OtherSlice","SomeSlice"
+"OtherSlice","SomeOtherSlice"
diff --git a/test/trace_processor/parsing/index b/test/trace_processor/parsing/index
index d21449f..2452955 100644
--- a/test/trace_processor/parsing/index
+++ b/test/trace_processor/parsing/index
@@ -62,6 +62,7 @@
 # Check the systrace conversion code in the raw table.
 # Print events
 ../../data/lmk_userspace.pb print_systrace.sql print_systrace_lmk_userspace.out
+kernel_tmw_counter.textproto thread_counter_and_track.sql kernel_tmw_counter_thread_counter_and_track.out
 # Unsigned integers
 print_systrace_unsigned.py print_systrace.sql print_systrace_unsigned.out
 
@@ -126,3 +127,18 @@
 
 # Ensures process -> package matching works as expected.
 process_metadata_matching.textproto process_metadata_matching.sql process_metadata_matching.out
+
+# Flow events importing from json
+flow_events_json_v1.json flow_events.sql flow_events_json_v1.out
+flow_events_json_v2.json flow_events.sql flow_events_json_v2.out
+
+# Importing displayTimeUnit
+../../data/display_time_unit.json slices.sql display_time_unit_slices.out
+
+# Parsing sched_blocked_reason
+sched_blocked_proto.py sched_blocked_reason.sql sched_blocked_proto_sched_blocked_reason.out
+sched_blocked_systrace.systrace sched_blocked_reason.sql sched_blocked_systrace_sched_blocked_reason.out
+
+# Kernel symbolization
+sched_blocked_reason_symbolized.textproto sched_blocked_reason_function.sql sched_blocked_reason_symbolized_sched_blocked_reason_function.out
+sched_blocked_reason_symbolized.textproto ../common/to_systrace.sql sched_blocked_reason_symbolized_to_systrace.out
diff --git a/test/trace_processor/parsing/kernel_tmw_counter.textproto b/test/trace_processor/parsing/kernel_tmw_counter.textproto
new file mode 100644
index 0000000..101f953
--- /dev/null
+++ b/test/trace_processor/parsing/kernel_tmw_counter.textproto
@@ -0,0 +1,77 @@
+packet {
+  ftrace_events {
+    cpu: 2
+    event {
+      timestamp: 795572805481
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_hw#15"
+        type: 67
+        value: 0
+      }
+    }
+    event {
+      timestamp: 795572870504
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_sw#15"
+        type: 67
+        value: 0
+      }
+    }
+    event {
+      timestamp: 795620516581
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_sw#15"
+        type: 67
+        value: 1
+      }
+    }
+    event {
+      timestamp: 795620943421
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_hw#15"
+        type: 67
+        value: 1
+      }
+    }
+    event {
+      timestamp: 795623633810
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_hw#15"
+        type: 67
+        value: 0
+      }
+    }
+    event {
+      timestamp: 795623633810
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_hw#15"
+        type: 67
+        value: 0
+      }
+    }
+    event {
+      timestamp: 795623739848
+      pid: 237
+      g2d_tracing_mark_write {
+        pid: 237
+        name: "g2d_frame_sw#15"
+        type: 67
+        value: 0
+      }
+    }
+  }
+  trusted_uid: 9999
+  trusted_packet_sequence_id: 3
+}
\ No newline at end of file
diff --git a/test/trace_processor/parsing/kernel_tmw_counter_thread_counter_and_track.out b/test/trace_processor/parsing/kernel_tmw_counter_thread_counter_and_track.out
new file mode 100644
index 0000000..a2037b9
--- /dev/null
+++ b/test/trace_processor/parsing/kernel_tmw_counter_thread_counter_and_track.out
@@ -0,0 +1,8 @@
+"ts","name","value","tid"
+795572805481,"g2d_frame_hw#15",0.000000,237
+795572870504,"g2d_frame_sw#15",0.000000,237
+795620516581,"g2d_frame_sw#15",1.000000,237
+795620943421,"g2d_frame_hw#15",1.000000,237
+795623633810,"g2d_frame_hw#15",0.000000,237
+795623633810,"g2d_frame_hw#15",0.000000,237
+795623739848,"g2d_frame_sw#15",0.000000,237
diff --git a/test/trace_processor/parsing/sched_blocked_proto.py b/test/trace_processor/parsing/sched_blocked_proto.py
new file mode 100644
index 0000000..a6589f8
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_proto.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# 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.
+
+from os import sys, path
+
+import synth_common
+
+file_member = 0
+anon_member = 1
+
+trace = synth_common.create_trace()
+trace.add_packet()
+trace.add_process(1, 0, "init")
+trace.add_process(2, 0, "init2")
+trace.add_process(3, 0, "unblocker")
+
+trace.add_ftrace_packet(0)
+trace.add_sched_blocked_reason(ts=100, pid=1, io_wait=0, unblock_pid=3)
+trace.add_sched_blocked_reason(ts=110, pid=2, io_wait=1, unblock_pid=3)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/parsing/sched_blocked_proto_sched_blocked_reason.out b/test/trace_processor/parsing/sched_blocked_proto_sched_blocked_reason.out
new file mode 100644
index 0000000..910c51c
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_proto_sched_blocked_reason.out
@@ -0,0 +1,3 @@
+"ts","tid","io_wait"
+100,1,0
+110,2,1
diff --git a/test/trace_processor/parsing/sched_blocked_reason.sql b/test/trace_processor/parsing/sched_blocked_reason.sql
new file mode 100644
index 0000000..59e26e6
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_reason.sql
@@ -0,0 +1,4 @@
+select ts, tid, EXTRACT_ARG(arg_set_id, 'io_wait') as io_wait
+from instants
+join thread on instants.ref = thread.utid
+where instants.name = 'sched_blocked_reason'
\ No newline at end of file
diff --git a/test/trace_processor/parsing/sched_blocked_reason_function.sql b/test/trace_processor/parsing/sched_blocked_reason_function.sql
new file mode 100644
index 0000000..1344d9b
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_reason_function.sql
@@ -0,0 +1,7 @@
+select
+  ts,
+  thread.tid as pid,
+  EXTRACT_ARG(arg_set_id, 'function') as func
+from instant
+join thread on instant.ref = thread.utid
+where instant.name = 'sched_blocked_reason';
\ No newline at end of file
diff --git a/test/trace_processor/parsing/sched_blocked_reason_symbolized.textproto b/test/trace_processor/parsing/sched_blocked_reason_symbolized.textproto
new file mode 100644
index 0000000..6b26df0
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_reason_symbolized.textproto
@@ -0,0 +1,112 @@
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 1000000
+      pid: 0
+      sched_blocked_reason {
+        pid: 100
+        caller: 1
+        io_wait: 0
+      }
+    }
+    event {
+      timestamp: 1001000
+      pid: 0
+      sched_blocked_reason {
+        pid: 101
+        caller: 2
+        io_wait: 0
+      }
+    }
+  }
+  trusted_uid: 0
+  trusted_packet_sequence_id: 2
+  interned_data {
+    kernel_symbols {
+      iid: 1
+      str: "filemap_fault"
+    }
+  }
+  sequence_flags: 1
+  previous_packet_dropped: true
+}
+packet {
+  ftrace_events {
+    cpu: 2
+    event {
+      timestamp: 999000
+      pid: 0
+      sched_blocked_reason {
+        pid: 102
+        caller: 1
+        io_wait: 1
+      }
+    }
+    event {
+      timestamp: 1002000
+      pid: 0
+      sched_blocked_reason {
+        pid: 103
+        caller: 2
+        io_wait: 1
+      }
+    }
+    event {
+      timestamp: 1005000
+      pid: 0
+      sched_blocked_reason {
+        pid: 104
+        caller: 1
+        io_wait: 1
+      }
+    }
+  }
+  trusted_uid: 0
+  trusted_packet_sequence_id: 2
+}
+packet {
+  ftrace_events {
+    cpu: 3
+    event {
+      timestamp: 999000
+      pid: 0
+      sched_blocked_reason {
+        pid: 105
+        caller: 3
+        io_wait: 1
+      }
+    }
+  }
+  trusted_uid: 0
+  trusted_packet_sequence_id: 2
+  interned_data {
+    kernel_symbols {
+      iid: 3
+      str: "some_fn"
+    }
+  }
+}
+packet {
+  ftrace_events {
+    cpu: 1
+    event {
+      timestamp: 1003000
+      pid: 0
+      sched_blocked_reason {
+        pid: 100
+        caller: 1
+        io_wait: 1
+      }
+    }
+  }
+  trusted_uid: 0
+  trusted_packet_sequence_id: 2
+  interned_data {
+    kernel_symbols {
+      iid: 1
+      str: "some_other_fn"
+    }
+  }
+  sequence_flags: 1
+}
\ No newline at end of file
diff --git a/test/trace_processor/parsing/sched_blocked_reason_symbolized_sched_blocked_reason_function.out b/test/trace_processor/parsing/sched_blocked_reason_symbolized_sched_blocked_reason_function.out
new file mode 100644
index 0000000..26bf1ac
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_reason_symbolized_sched_blocked_reason_function.out
@@ -0,0 +1,8 @@
+"ts","pid","func"
+999000,102,"filemap_fault"
+999000,105,"some_fn"
+1000000,100,"filemap_fault"
+1001000,101,"[NULL]"
+1002000,103,"[NULL]"
+1003000,100,"some_other_fn"
+1005000,104,"filemap_fault"
diff --git a/test/trace_processor/parsing/sched_blocked_reason_symbolized_to_systrace.out b/test/trace_processor/parsing/sched_blocked_reason_symbolized_to_systrace.out
new file mode 100644
index 0000000..ea78ae5
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_reason_symbolized_to_systrace.out
@@ -0,0 +1,8 @@
+"line"
+"          <idle>-0     (-----) [002] .... 0.000999: sched_blocked_reason: pid=102 io_wait=1 caller=filemap_fault"
+"          <idle>-0     (-----) [003] .... 0.000999: sched_blocked_reason: pid=105 io_wait=1 caller=some_fn"
+"          <idle>-0     (-----) [001] .... 0.001000: sched_blocked_reason: pid=100 io_wait=0 caller=filemap_fault"
+"          <idle>-0     (-----) [001] .... 0.001001: sched_blocked_reason: pid=101 io_wait=0 caller=2"
+"          <idle>-0     (-----) [002] .... 0.001002: sched_blocked_reason: pid=103 io_wait=1 caller=2"
+"          <idle>-0     (-----) [001] .... 0.001003: sched_blocked_reason: pid=100 io_wait=1 caller=some_other_fn"
+"          <idle>-0     (-----) [002] .... 0.001005: sched_blocked_reason: pid=104 io_wait=1 caller=filemap_fault"
diff --git a/test/trace_processor/parsing/sched_blocked_systrace.systrace b/test/trace_processor/parsing/sched_blocked_systrace.systrace
new file mode 100644
index 0000000..81046bf
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_systrace.systrace
@@ -0,0 +1,2 @@
+           <...>-3694  ( 3694) [006] d..3    20.258854: sched_blocked_reason: pid=269 iowait=0 caller=worker_thread+0x534/0x820
+          <idle>-0     (-----) [000] d.s4    21.123838: sched_blocked_reason: pid=2172 iowait=1 caller=__filemap_fdatawait_range+0x134/0x150
diff --git a/test/trace_processor/parsing/sched_blocked_systrace_sched_blocked_reason.out b/test/trace_processor/parsing/sched_blocked_systrace_sched_blocked_reason.out
new file mode 100644
index 0000000..87f718c
--- /dev/null
+++ b/test/trace_processor/parsing/sched_blocked_systrace_sched_blocked_reason.out
@@ -0,0 +1,3 @@
+"ts","tid","io_wait"
+20258854000,269,0
+21123838000,2172,1
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/parsing/slices.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/parsing/slices.sql
index cdd953a..d41bd3b 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/parsing/slices.sql
@@ -13,13 +13,5 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
+select ts, dur, name from slice order by ts desc;
 
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
diff --git a/test/trace_processor/parsing/thread_counter_and_track.sql b/test/trace_processor/parsing/thread_counter_and_track.sql
new file mode 100644
index 0000000..24f3d81
--- /dev/null
+++ b/test/trace_processor/parsing/thread_counter_and_track.sql
@@ -0,0 +1,5 @@
+select ts, t.name, value, tid
+from counter c
+join thread_counter_track t on c.track_id = t.id
+join thread using (utid)
+order by ts;
\ No newline at end of file
diff --git a/test/trace_processor/parsing/thread_time_in_state.textproto b/test/trace_processor/parsing/thread_time_in_state.textproto
index 582c0d5..f6d4749 100644
--- a/test/trace_processor/parsing/thread_time_in_state.textproto
+++ b/test/trace_processor/parsing/thread_time_in_state.textproto
@@ -1,5 +1,7 @@
 packet {
   system_info {
+    # Use bonito CPU core to cluster mapping.
+    android_build_fingerprint: "bonito"
     hz: 100
   }
 }
diff --git a/test/trace_processor/profiling/heap_graph.textproto b/test/trace_processor/profiling/heap_graph.textproto
index b0a304d..a05b3f4 100644
--- a/test/trace_processor/profiling/heap_graph.textproto
+++ b/test/trace_processor/profiling/heap_graph.textproto
@@ -49,10 +49,7 @@
     objects {
       id: 0x01
       type_id: 1
-      self_size: 64
-      reference_field_id: 1
       reference_object_id: 0x02
-      reference_field_id: 3
       reference_object_id: 0x02
     }
     objects {
@@ -63,12 +60,10 @@
     objects {
       id: 0x03
       type_id: 2
-      self_size: 128
     }
     objects {
       id: 0x04
       type_id: 3
-      self_size: 256
       reference_field_id: 2
       reference_object_id: 0x01
     }
@@ -93,22 +88,41 @@
     pid: 2
     location_names {
       iid: 1
-      str: "/data/app/ASDFG/invalid.test.android-SDASD/test.apk"
+      str: "/data/app/~~ASDFG==/invalid.test.android-SDASD/test.apk"
+    }
+    types {
+      id: 6
+      class_name: "FactoryProducerDelegateImplActorEmptySuper"
+      location_id: 1
+      object_size: 64
+      superclass_id: 7
+    }
+    types {
+      id: 7
+      class_name: "FactoryProducerDelegateImplActorSuperSuper"
+      location_id: 1
+      object_size: 64
+      reference_field_id: 3
     }
     types {
       id: 1
       class_name: "FactoryProducerDelegateImplActor"
       location_id: 1
+      object_size: 64
+      reference_field_id: 1
+      superclass_id: 6
     }
     types {
       id: 2
       class_name: "Foo"
       location_id: 1
+      object_size: 128
     }
     types {
       id: 3
       class_name: "a"
       location_id: 1
+      object_size: 256
     }
     types {
       id: 4
diff --git a/test/trace_processor/profiling/heap_graph_branching.textproto b/test/trace_processor/profiling/heap_graph_branching.textproto
index d876fd0..7690c77 100644
--- a/test/trace_processor/profiling/heap_graph_branching.textproto
+++ b/test/trace_processor/profiling/heap_graph_branching.textproto
@@ -64,25 +64,25 @@
   trusted_packet_sequence_id: 999
   heap_graph {
     pid: 2
-    type_names {
-      iid: 1
-      str: "RootNode"
+    types {
+      id: 1
+      class_name: "RootNode"
     }
-    type_names {
-      iid: 2
-      str: "LeftChild0"
+    types {
+      id: 2
+      class_name: "LeftChild0"
     }
-    type_names {
-      iid: 3
-      str: "LeftChild1"
+    types {
+      id: 3
+      class_name: "LeftChild1"
     }
-    type_names {
-      iid: 4
-      str: "RightChild0"
+    types {
+      id: 4
+      class_name: "RightChild0"
     }
-    type_names {
-      iid: 5
-      str: "RightChild1"
+    types {
+      id: 5
+      class_name: "RightChild1"
     }
     field_names {
       iid: 1
diff --git a/test/trace_processor/profiling/heap_graph_flamegraph_system-server-heap-graph.out b/test/trace_processor/profiling/heap_graph_flamegraph_system-server-heap-graph.out
index c31c986..810924b 100644
--- a/test/trace_processor/profiling/heap_graph_flamegraph_system-server-heap-graph.out
+++ b/test/trace_processor/profiling/heap_graph_flamegraph_system-server-heap-graph.out
@@ -1,11 +1,11 @@
 "id","depth","name","map_name","count","cumulative_count","size","cumulative_size","parent_id"
-0,0,"java.lang.Class<java.util.concurrent.locks.AbstractQueuedSynchronizer$Node>","JAVA",1,3,316,356,"[NULL]"
-1,1,"java.lang.Object[]","JAVA",1,1,12,12,0
-2,1,"java.util.concurrent.locks.AbstractQueuedSynchronizer$Node","JAVA",1,1,28,28,0
-3,0,"java.util.concurrent.locks.AbstractQueuedSynchronizer$Node","JAVA",2,2,56,56,"[NULL]"
-4,0,"java.lang.Thread","JAVA",41,2854,4838,122801,"[NULL]"
-5,1,"dalvik.system.PathClassLoader","JAVA",1,42,48,1312,4
-6,2,"java.util.HashMap","JAVA",2,2,80,80,5
-7,2,"dalvik.system.DexPathList","JAVA",1,39,32,1184,5
-8,3,"dalvik.system.DexPathList$Element[]","JAVA",1,21,28,672,7
-9,4,"dalvik.system.DexPathList$Element","JAVA",4,20,100,644,8
+0,0,"java.lang.Class<java.lang.Object[]>","JAVA",1,3,224,292,"[NULL]"
+1,1,"java.lang.Object[]","JAVA",1,1,28,28,0
+2,1,"java.lang.String","JAVA",1,1,40,40,0
+3,0,"java.lang.String","JAVA",41233,41233,1846824,1846824,"[NULL]"
+4,0,"java.lang.Class<android.content.pm.ApplicationInfo>","JAVA",1,5,1152,1268,"[NULL]"
+5,1,"java.lang.String","JAVA",1,1,56,56,4
+6,1,"android.content.pm.ApplicationInfo$1","JAVA",1,1,8,8,4
+7,1,"java.lang.Object[]","JAVA",1,2,20,52,4
+8,2,"long[]","JAVA",1,1,32,32,7
+9,0,"java.lang.Class<java.io.File>","JAVA",1,11,645,997,"[NULL]"
diff --git a/test/trace_processor/profiling/heap_graph_interleaved.textproto b/test/trace_processor/profiling/heap_graph_interleaved.textproto
index fa736a4..2e2af30 100644
--- a/test/trace_processor/profiling/heap_graph_interleaved.textproto
+++ b/test/trace_processor/profiling/heap_graph_interleaved.textproto
@@ -84,7 +84,7 @@
     pid: 2
     location_names {
       iid: 1
-      str: "/data/app/ASDFG/invalid.test.android-SDASD/test.apk"
+      str: "/data/app/~~ASDFG==/invalid.test.android-SDASD/test.apk"
     }
     types {
       id: 1
diff --git a/test/trace_processor/profiling/heap_graph_legacy.textproto b/test/trace_processor/profiling/heap_graph_legacy.textproto
index 13fc018..68a8e54 100644
--- a/test/trace_processor/profiling/heap_graph_legacy.textproto
+++ b/test/trace_processor/profiling/heap_graph_legacy.textproto
@@ -91,25 +91,25 @@
   timestamp: 10
   heap_graph {
     pid: 2
-    type_names {
-      iid: 1
-      str: "FactoryProducerDelegateImplActor"
+    types {
+      id: 1
+      class_name: "FactoryProducerDelegateImplActor"
     }
-    type_names {
-      iid: 2
-      str: "Foo"
+    types {
+      id: 2
+      class_name: "Foo"
     }
-    type_names {
-      iid: 3
-      str: "a"
+    types {
+      id: 3
+      class_name: "a"
     }
-    type_names {
-      iid: 4
-      str: "a[]"
+    types {
+      id: 4
+      class_name: "a[]"
     }
-    type_names {
-      iid: 5
-      str: "java.lang.Class<a[]>"
+    types {
+      id: 5
+      class_name: "java.lang.Class<a[]>"
     }
     field_names {
       iid: 1
diff --git a/test/trace_processor/profiling/heap_graph_two_locations.textproto b/test/trace_processor/profiling/heap_graph_two_locations.textproto
index e70e525..59a8ccf 100644
--- a/test/trace_processor/profiling/heap_graph_two_locations.textproto
+++ b/test/trace_processor/profiling/heap_graph_two_locations.textproto
@@ -93,11 +93,11 @@
     pid: 2
     location_names {
       iid: 1
-      str: "/data/app/ASDFG/invalid.test.android-SDASD/test.apk"
+      str: "/data/app/~~ASDFG==/invalid.test.android-SDASD/test.apk"
     }
     location_names {
       iid: 2
-      str: "/data/app/ASDFG/invalid.test2.android-SDASD/test.apk"
+      str: "/data/app/~~ASDFG==/invalid.test2.android-SDASD/test.apk"
     }
     types {
       id: 1
diff --git a/test/trace_processor/profiling/heap_profile_deobfuscate.out b/test/trace_processor/profiling/heap_profile_deobfuscate.out
new file mode 100644
index 0000000..4381edc
--- /dev/null
+++ b/test/trace_processor/profiling/heap_profile_deobfuscate.out
@@ -0,0 +1,2 @@
+"deobfuscated_name","mapping","rel_pc"
+"Bar.function1",0,4096
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/profiling/heap_profile_deobfuscate.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/profiling/heap_profile_deobfuscate.sql
index cdd953a..c0260b4 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/profiling/heap_profile_deobfuscate.sql
@@ -13,13 +13,4 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+SELECT deobfuscated_name, mapping, rel_pc FROM stack_profile_frame ORDER BY name;
diff --git a/test/trace_processor/profiling/heap_profile_deobfuscate.textproto b/test/trace_processor/profiling/heap_profile_deobfuscate.textproto
new file mode 100644
index 0000000..1782b1c
--- /dev/null
+++ b/test/trace_processor/profiling/heap_profile_deobfuscate.textproto
@@ -0,0 +1,102 @@
+packet {
+  process_tree {
+    processes {
+      pid: 1
+      ppid: 0
+      cmdline: "init"
+      uid: 0
+    }
+    processes {
+      pid: 2
+      ppid: 1
+      cmdline: "system_server"
+      uid: 1000
+    }
+  }
+}
+
+packet {
+  clock_snapshot {
+    clocks: {
+      clock_id: 6 # BOOTTIME
+      timestamp: 0
+    }
+    clocks: {
+      clock_id: 4 # MONOTONIC_COARSE
+      timestamp: 10
+    }
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 999
+  previous_packet_dropped: true
+  incremental_state_cleared: true
+  timestamp: 10
+  profile_packet {
+    strings {
+      iid: 1
+      str: "a.f1"
+    }
+    strings {
+      iid: 2
+      str: "data/app/com.google.android.webview-6XfQhnaSkFwGK0sYL9is0G==/base.apk"
+    }
+    strings {
+      iid: 3
+      str: "build-id"
+    }
+    frames {
+      iid: 1
+      function_name_id: 1
+      mapping_id: 1
+      rel_pc: 0x1000
+    }
+    callstacks {
+      iid: 1
+      frame_ids: 1
+      frame_ids: 1
+    }
+    mappings {
+      iid: 1
+      path_string_ids: 2
+      build_id: 3
+    }
+    process_dumps {
+      pid: 2
+      samples {
+        callstack_id: 1
+        self_allocated: 2000
+        self_freed: 1000
+        alloc_count: 2
+        free_count: 1
+      }
+    }
+  }
+}
+packet {
+  deobfuscation_mapping {
+    package_name: "com.google.android.wrong"
+    version_code: 1234
+    obfuscated_classes {
+      obfuscated_name: "a"
+      deobfuscated_name: "Foo"
+      obfuscated_methods {
+        obfuscated_name: "f1"
+        deobfuscated_name: "function2"
+      }
+    }
+  }
+  deobfuscation_mapping {
+    package_name: "com.google.android.webview"
+    version_code: 1234
+    obfuscated_classes {
+      obfuscated_name: "a"
+      deobfuscated_name: "Bar"
+      obfuscated_methods {
+        obfuscated_name: "f1"
+        deobfuscated_name: "function1"
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/profiling/heap_profile_deobfuscate_memfd.textproto b/test/trace_processor/profiling/heap_profile_deobfuscate_memfd.textproto
new file mode 100644
index 0000000..4fdc403
--- /dev/null
+++ b/test/trace_processor/profiling/heap_profile_deobfuscate_memfd.textproto
@@ -0,0 +1,90 @@
+packet {
+  process_tree {
+    processes {
+      pid: 1
+      ppid: 0
+      cmdline: "init"
+      uid: 0
+    }
+    processes {
+      pid: 2
+      ppid: 1
+      cmdline: "system_server"
+      uid: 1000
+    }
+  }
+}
+
+packet {
+  clock_snapshot {
+    clocks: {
+      clock_id: 6 # BOOTTIME
+      timestamp: 0
+    }
+    clocks: {
+      clock_id: 4 # MONOTONIC_COARSE
+      timestamp: 10
+    }
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 999
+  previous_packet_dropped: true
+  incremental_state_cleared: true
+  timestamp: 10
+  profile_packet {
+    strings {
+      iid: 1
+      str: "a.f1"
+    }
+    strings {
+      iid: 2
+      str: "memfd:jit-cache"
+    }
+    strings {
+      iid: 3
+      str: "build-id"
+    }
+    frames {
+      iid: 1
+      function_name_id: 1
+      mapping_id: 1
+      rel_pc: 0x1000
+    }
+    callstacks {
+      iid: 1
+      frame_ids: 1
+      frame_ids: 1
+    }
+    mappings {
+      iid: 1
+      path_string_ids: 2
+      build_id: 3
+    }
+    process_dumps {
+      pid: 2
+      samples {
+        callstack_id: 1
+        self_allocated: 2000
+        self_freed: 1000
+        alloc_count: 2
+        free_count: 1
+      }
+    }
+  }
+}
+packet {
+  deobfuscation_mapping {
+    package_name: "com.google.android.webview"
+    version_code: 1234
+    obfuscated_classes {
+      obfuscated_name: "a"
+      deobfuscated_name: "Bar"
+      obfuscated_methods {
+        obfuscated_name: "f1"
+        deobfuscated_name: "function1"
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/profiling/index b/test/trace_processor/profiling/index
index 5f82730..d216ac5 100644
--- a/test/trace_processor/profiling/index
+++ b/test/trace_processor/profiling/index
@@ -1,6 +1,9 @@
 # Contains heap profiling, perf profiling and heap graph tests.
 
 heap_profile_jit.textproto heap_profile_frames.sql heap_profile_jit.out
+heap_profile_deobfuscate.textproto heap_profile_deobfuscate.sql heap_profile_deobfuscate.out
+heap_profile_deobfuscate_memfd.textproto heap_profile_deobfuscate.sql heap_profile_deobfuscate.out
+
 
 profiler_smaps.textproto profiler_smaps.sql profiler_smaps.out
 
@@ -18,7 +21,7 @@
 heap_graph_legacy.textproto heap_graph_reference.sql heap_graph_reference.out
 heap_graph_interleaved.textproto heap_graph_object.sql heap_graph_interleaved_object.out
 heap_graph_interleaved.textproto heap_graph_reference.sql heap_graph_interleaved_reference.out
-../../data/system-server-heap-graph.pftrace heap_graph_flamegraph.sql heap_graph_flamegraph_system-server-heap-graph.out
+../../data/system-server-heap-graph-new.pftrace heap_graph_flamegraph.sql heap_graph_flamegraph_system-server-heap-graph.out
 ../../data/system-server-native-profile heap_profile_flamegraph.sql heap_profile_flamegraph_system-server-native-profile.out
 heap_profile_tracker_new_stack.textproto heap_profile_tracker_new_stack.sql heap_profile_tracker_new_stack.out
 heap_profile_tracker_twoheaps.textproto heap_profile_tracker_twoheaps.sql heap_profile_tracker_twoheaps.out
@@ -39,7 +42,6 @@
 heap_graph.textproto java_heap_stats java_heap_stats.out
 heap_graph_closest_proc.textproto java_heap_stats heap_stats_closest_proc.out
 heap_graph.textproto java_heap_histogram java_heap_histogram.out
-obfuscated_heap_graph.textproto unmapped_java_symbols unmapped_java_symbols.out
 
 # perf_sample table (traced_perf trace as an input).
 ../../data/perf_sample.pb perf_sample.sql perf_sample_perf_sample.out
diff --git a/test/trace_processor/profiling/java_heap_histogram.out b/test/trace_processor/profiling/java_heap_histogram.out
index 8558371..824b68c 100644
--- a/test/trace_processor/profiling/java_heap_histogram.out
+++ b/test/trace_processor/profiling/java_heap_histogram.out
@@ -8,11 +8,6 @@
     samples {
       ts: 10
       type_count {
-        type_name: "DeobfuscatedA"
-        obj_count: 1
-        reachable_obj_count: 0
-      }
-      type_count {
         type_name: "DeobfuscatedA[]"
         obj_count: 1
         reachable_obj_count: 1
@@ -28,6 +23,11 @@
         reachable_obj_count: 1
       }
       type_count {
+        type_name: "DeobfuscatedA"
+        obj_count: 1
+        reachable_obj_count: 0
+      }
+      type_count {
         type_name: "java.lang.Class<DeobfuscatedA[]>"
         obj_count: 1
         reachable_obj_count: 0
diff --git a/test/trace_processor/profiling/obfuscated_heap_graph.textproto b/test/trace_processor/profiling/obfuscated_heap_graph.textproto
deleted file mode 100644
index 4eb9ecf..0000000
--- a/test/trace_processor/profiling/obfuscated_heap_graph.textproto
+++ /dev/null
@@ -1,76 +0,0 @@
-packet {
-  process_tree {
-    processes {
-      pid: 1
-      ppid: 0
-      cmdline: "init"
-      uid: 0
-    }
-    processes {
-      pid: 2
-      ppid: 1
-      cmdline: "com.google.android.gm"
-      uid: 10001
-    }
-  }
-}
-packet {
-  trusted_packet_sequence_id: 999
-  timestamp: 10
-  heap_graph {
-    pid: 2
-    roots {
-      root_type: ROOT_JAVA_FRAME
-      object_ids: 0x01
-    }
-    objects {
-      id: 0x01
-      type_id: 1
-      self_size: 64
-      reference_field_id: 1
-      reference_object_id: 0x02
-      reference_field_id: 2
-      reference_object_id: 0x01
-    }
-    objects {
-      id: 0x02
-      type_id: 2
-      self_size: 32
-    }
-    objects {
-      id: 0x03
-      type_id: 3
-      self_size: 32
-    }
-    continued: true
-    index: 1
-  }
-}
-packet {
-  trusted_packet_sequence_id: 999
-  heap_graph {
-    pid: 2
-    type_names {
-      iid: 1
-      str: "FactoryProducerDelegateImplActor"
-    }
-    type_names {
-      iid: 2
-      str: "a"
-    }
-    type_names {
-      iid: 3
-      str: "java.lang.Class<abc[]>"
-    }
-    field_names {
-      iid: 1
-      str: "FactoryProducerDelegateImplActor.a"
-    }
-    field_names {
-      iid: 2
-      str: "FactoryProducerDelegateImplActor FactoryProducerDelegateImplActor.b"
-    }
-    continued: false
-    index: 2
-  }
-}
diff --git a/test/trace_processor/profiling/unmapped_java_symbols.out b/test/trace_processor/profiling/unmapped_java_symbols.out
deleted file mode 100644
index 4628dd9..0000000
--- a/test/trace_processor/profiling/unmapped_java_symbols.out
+++ /dev/null
@@ -1,18 +0,0 @@
-unmapped_java_symbols {
-  process_symbols {
-    process_metadata {
-      name: "com.google.android.gm"
-      uid: 10001
-    }
-    type_name: "FactoryProducerDelegateImplActor"
-    type_name: "a"
-    type_name: "abc"
-    field {
-      field_name: "FactoryProducerDelegateImplActor.a"
-    }
-    field {
-      field_name: "FactoryProducerDelegateImplActor.b"
-      field_type_name: "FactoryProducerDelegateImplActor"
-    }
-  }
-}
diff --git a/test/trace_processor/python/api_integrationtest.py b/test/trace_processor/python/api_integrationtest.py
new file mode 100644
index 0000000..392e28e
--- /dev/null
+++ b/test/trace_processor/python/api_integrationtest.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# 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.
+
+import os
+import unittest
+
+from trace_processor.api import TraceProcessor
+
+
+class TestApi(unittest.TestCase):
+
+  def test_trace_file(self):
+    # Get path to trace_processor_shell and construct TraceProcessor
+    tp = TraceProcessor(
+        file_path=os.path.join(os.environ["ROOT_DIR"], 'test', 'data',
+                               'example_android_trace_30s.pb'),
+        bin_path=os.environ["SHELL_PATH"])
+    qr_iterator = tp.query('select * from slice limit 10')
+    dur_result = [
+        178646, 119740, 58073, 155000, 173177, 20209377, 3589167, 90104, 275312,
+        65313
+    ]
+
+    for num, row in enumerate(qr_iterator):
+      self.assertEqual(row.type, 'internal_slice')
+      self.assertEqual(row.dur, dur_result[num])
+
+    # Test the batching logic by issuing a large query and ensuring we receive
+    # all rows, not just a truncated subset.
+    qr_iterator = tp.query('select count(*) as cnt from slice')
+    expected_count = next(qr_iterator).cnt
+    self.assertGreater(expected_count, 0)
+
+    qr_iterator = tp.query('select * from slice')
+    count = sum(1 for _ in qr_iterator)
+    self.assertEqual(count, expected_count)
+
+    tp.close()
diff --git a/test/trace_processor/python/api_unittest.py b/test/trace_processor/python/api_unittest.py
new file mode 100755
index 0000000..52e2c41
--- /dev/null
+++ b/test/trace_processor/python/api_unittest.py
@@ -0,0 +1,292 @@
+#!/usr/bin/env python3
+# 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.
+
+import unittest
+
+from trace_processor.api import TraceProcessor, TraceProcessorException
+from trace_processor.protos import ProtoFactory
+
+
+class TestQueryResultIterator(unittest.TestCase):
+  # The numbers input into cells correspond the the CellType enum values
+  # defined under trace_processor.proto
+  CELL_VARINT = ProtoFactory().CellsBatch().CELL_VARINT
+  CELL_STRING = ProtoFactory().CellsBatch().CELL_STRING
+  CELL_INVALID = ProtoFactory().CellsBatch().CELL_INVALID
+
+  def test_one_batch(self):
+    int_values = [100, 200]
+    str_values = ['bar1', 'bar2']
+
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend(int_values)
+    batch.string_cells = "\0".join(str_values) + "\0"
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    for num, row in enumerate(qr_iterator):
+      self.assertEqual(row.foo_id, str_values[num])
+      self.assertEqual(row.foo_num, int_values[num])
+
+  def test_many_batches(self):
+    int_values = [100, 200, 300, 400]
+    str_values = ['bar1', 'bar2', 'bar3', 'bar4']
+
+    batch_1 = ProtoFactory().CellsBatch()
+    batch_1.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch_1.varint_cells.extend(int_values[:2])
+    batch_1.string_cells = "\0".join(str_values[:2]) + "\0"
+    batch_1.is_last_batch = False
+
+    batch_2 = ProtoFactory().CellsBatch()
+    batch_2.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch_2.varint_cells.extend(int_values[2:])
+    batch_2.string_cells = "\0".join(str_values[2:]) + "\0"
+    batch_2.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch_1, batch_2])
+
+    for num, row in enumerate(qr_iterator):
+      self.assertEqual(row.foo_id, str_values[num])
+      self.assertEqual(row.foo_num, int_values[num])
+
+  def test_empty_batch(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
+
+    for num, row in enumerate(qr_iterator):
+      self.assertIsNone(row.foo_id)
+      self.assertIsNone(row.foo_num)
+
+  def test_invalid_batch(self):
+    batch = ProtoFactory().CellsBatch()
+
+    qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
+
+    # Since the batch isn't defined as the last batch, the QueryResultsIterator
+    # expects another batch and thus raises IndexError as no next batch exists.
+    with self.assertRaises(IndexError):
+      for row in qr_iterator:
+        pass
+
+  def test_incorrect_cells_batch(self):
+    str_values = ['bar1', 'bar2']
+
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.string_cells = "\0".join(str_values) + "\0"
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    # The batch specifies there ought to be 2 cells of type VARINT and 2 cells
+    # of type STRING, but there are no string cells defined in the batch. Thus
+    # an IndexError occurs as it tries to access the empty string cells list.
+    with self.assertRaises(IndexError):
+      for row in qr_iterator:
+        pass
+
+  def test_incorrect_columns_batch(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_VARINT, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend([100, 200])
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(
+        ['foo_id', 'foo_num', 'foo_dur', 'foo_ms'], [batch])
+
+    # It's always the case that the number of cells is a multiple of the number
+    # of columns. However, here this is clearly not the case, so when the
+    # iterator tries to access the cell for the third column, it raises an
+    # IndexError due to having exhausted the cells list.
+    with self.assertRaises(IndexError):
+      for row in qr_iterator:
+        pass
+
+  def test_invalid_cell_type(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_INVALID,
+        TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend([100, 200])
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    # In this batch we declare the columns types to be CELL_INVALID,
+    # CELL_VARINT but that doesn't match the data which are both ints*
+    # so we should raise a TraceProcessorException.
+    with self.assertRaises(TraceProcessorException):
+      for row in qr_iterator:
+        pass
+
+  def test_one_batch_as_pandas(self):
+    int_values = [100, 200]
+    str_values = ['bar1', 'bar2']
+
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend(int_values)
+    batch.string_cells = "\0".join(str_values) + "\0"
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    qr_df = qr_iterator.as_pandas_dataframe()
+    for num, row in qr_df.iterrows():
+      self.assertEqual(row['foo_id'], str_values[num])
+      self.assertEqual(row['foo_num'], int_values[num])
+
+  def test_many_batches_as_pandas(self):
+    int_values = [100, 200, 300, 400]
+    str_values = ['bar1', 'bar2', 'bar3', 'bar4']
+
+    batch_1 = ProtoFactory().CellsBatch()
+    batch_1.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch_1.varint_cells.extend(int_values[:2])
+    batch_1.string_cells = "\0".join(str_values[:2]) + "\0"
+    batch_1.is_last_batch = False
+
+    batch_2 = ProtoFactory().CellsBatch()
+    batch_2.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch_2.varint_cells.extend(int_values[2:])
+    batch_2.string_cells = "\0".join(str_values[2:]) + "\0"
+    batch_2.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch_1, batch_2])
+
+    qr_df = qr_iterator.as_pandas_dataframe()
+    for num, row in qr_df.iterrows():
+      self.assertEqual(row['foo_id'], str_values[num])
+      self.assertEqual(row['foo_num'], int_values[num])
+
+  def test_empty_batch_as_pandas(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
+
+    qr_df = qr_iterator.as_pandas_dataframe()
+    for num, row in qr_df.iterrows():
+      self.assertEqual(row['foo_id'], str_values[num])
+      self.assertEqual(row['foo_num'], int_values[num])
+
+  def test_invalid_batch_as_pandas(self):
+    batch = ProtoFactory().CellsBatch()
+
+    qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
+
+    # Since the batch isn't defined as the last batch, the QueryResultsIterator
+    # expects another batch and thus raises IndexError as no next batch exists.
+    with self.assertRaises(IndexError):
+      qr_df = qr_iterator.as_pandas_dataframe()
+
+  def test_incorrect_cells_batch_as_pandas(self):
+    str_values = ['bar1', 'bar2']
+
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_STRING,
+        TestQueryResultIterator.CELL_VARINT,
+        TestQueryResultIterator.CELL_STRING, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.string_cells = "\0".join(str_values) + "\0"
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    # The batch specifies there ought to be 2 cells of type VARINT and 2 cells
+    # of type STRING, but there are no string cells defined in the batch. Thus
+    # an IndexError occurs as it tries to access the empty string cells list.
+    with self.assertRaises(IndexError):
+      qr_df = qr_iterator.as_pandas_dataframe()
+
+  def test_incorrect_columns_batch_as_pandas(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_VARINT, TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend([100, 200])
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(
+        ['foo_id', 'foo_num', 'foo_dur', 'foo_ms'], [batch])
+
+    # It's always the case that the number of cells is a multiple of the number
+    # of columns. However, here this is clearly not the case, so when the
+    # iterator tries to access the cell for the third column, it raises an
+    # IndexError due to having exhausted the cells list.
+    with self.assertRaises(IndexError):
+      qr_df = qr_iterator.as_pandas_dataframe()
+
+  def test_invalid_cell_type_as_pandas(self):
+    batch = ProtoFactory().CellsBatch()
+    batch.cells.extend([
+        TestQueryResultIterator.CELL_INVALID,
+        TestQueryResultIterator.CELL_VARINT
+    ])
+    batch.varint_cells.extend([100, 200])
+    batch.is_last_batch = True
+
+    qr_iterator = TraceProcessor.QueryResultIterator(['foo_id', 'foo_num'],
+                                                     [batch])
+
+    # In this batch we declare the columns types to be CELL_INVALID,
+    # CELL_VARINT but that doesn't match the data which are both ints*
+    # so we should raise a TraceProcessorException.
+    with self.assertRaises(TraceProcessorException):
+      qr_df = qr_iterator.as_pandas_dataframe()
diff --git a/test/trace_processor/smoke/index b/test/trace_processor/smoke/index
index 4fe0ecb..ebc2042 100644
--- a/test/trace_processor/smoke/index
+++ b/test/trace_processor/smoke/index
@@ -18,3 +18,6 @@
 
 # Compute CPU time metric testing several core tables.
 ../../data/example_android_trace_30s.pb thread_cpu_time.sql thread_cpu_time_example_android_trace_30s.out
+
+# Compute power proxy metric
+../../data/cpu_counters.pb proxy_power.sql proxy_power.out
diff --git a/test/trace_processor/smoke/proxy_power.out b/test/trace_processor/smoke/proxy_power.out
new file mode 100644
index 0000000..d57cfc5
--- /dev/null
+++ b/test/trace_processor/smoke/proxy_power.out
@@ -0,0 +1,15 @@
+
+
+
+
+"utid","power_mas"
+122,80.118876
+121,73.090167
+140,54.378097
+31,15.297968
+161,13.740925
+23,12.703770
+205,11.924118
+43,10.011150
+258,9.165919
+1,9.137632
diff --git a/test/trace_processor/smoke/proxy_power.sql b/test/trace_processor/smoke/proxy_power.sql
new file mode 100644
index 0000000..5f055ab
--- /dev/null
+++ b/test/trace_processor/smoke/proxy_power.sql
@@ -0,0 +1,34 @@
+--
+-- Copyright 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
+--
+--     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 RUN_METRIC('android/android_proxy_power.sql') AS suppress_query_output;
+
+-- The test trace doesn't contain metadata necessary to determine the device
+-- name, so we create a table with the name directly.
+DROP VIEW device;
+
+CREATE TABLE device (name STRING);
+
+INSERT INTO device VALUES ('walleye');
+
+-- Select the top 10 threads by power usage.
+SELECT
+  utid,
+  SUM(dur * COALESCE(power_ma, 0) / 1e9) AS power_mas
+FROM power_per_thread
+GROUP BY utid
+ORDER BY power_mas DESC
+LIMIT 10;
diff --git a/src/trace_processor/metrics/android/counter_span_view.sql b/test/trace_processor/track_event/flow_events.sql
similarity index 66%
copy from src/trace_processor/metrics/android/counter_span_view.sql
copy to test/trace_processor/track_event/flow_events.sql
index cdd953a..11ef1a9 100644
--- a/src/trace_processor/metrics/android/counter_span_view.sql
+++ b/test/trace_processor/track_event/flow_events.sql
@@ -13,13 +13,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
-CREATE VIEW IF NOT EXISTS {{table_name}}_span AS
-SELECT
-  ts,
-  LEAD(ts, 1, (SELECT end_ts + 1 FROM trace_bounds))
-      OVER(PARTITION BY track_id ORDER BY ts) - ts AS dur,
-  value AS {{table_name}}_val
-FROM counter c JOIN counter_track t
-  ON t.id = c.track_id
-WHERE name = '{{counter_name}}';
+select t1.name as slice_out, t2.name as slice_in from flow t
+join slice t1 on t.slice_out == t1.slice_id
+join slice t2 on t.slice_in == t2.slice_id;
diff --git a/test/trace_processor/track_event/flow_events_proto_v1.out b/test/trace_processor/track_event/flow_events_proto_v1.out
new file mode 100644
index 0000000..b9283ed
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_proto_v1.out
@@ -0,0 +1,4 @@
+"slice_out","slice_in"
+"FlowBeginSlice","FlowEndSlice_1"
+"FlowEndSlice_1","FlowStepSlice"
+"FlowStepSlice","FlowEndSlice_2"
diff --git a/test/trace_processor/track_event/flow_events_proto_v1.textproto b/test/trace_processor/track_event/flow_events_proto_v1.textproto
new file mode 100644
index 0000000..19c1265
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_proto_v1.textproto
@@ -0,0 +1,142 @@
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 1
+    thread {
+      pid: 1
+      tid: 1
+      thread_name: "t1"
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 2
+    thread {
+      pid: 2
+      tid: 2
+      thread_name: "t2"
+    }
+  }
+}
+packet {
+  timestamp: 10000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowBeginSlice"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 10000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "Flow330"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      phase: 115 # 's'
+      unscoped_id: 330
+    }
+  }
+}
+packet {
+  timestamp: 29999
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "Flow330"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      phase: 102 # 'f'
+      unscoped_id: 330
+    }
+  }
+}
+packet {
+  timestamp: 30000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowEndSlice_1"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 30001
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "Flow331"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      phase: 115 # 's'
+      unscoped_id: 331
+    }
+  }
+}
+packet {
+  timestamp: 50000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowStepSlice"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 50100
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "Flow331"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      phase: 116 # 't'
+      unscoped_id: 331
+    }
+  }
+}
+packet {
+  timestamp: 55000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowEndSlice_2"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 55100
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "Flow331"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      phase: 102 # 'f'
+      bind_to_enclosing: true
+      unscoped_id: 331
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/trace_processor/track_event/flow_events_proto_v2.out b/test/trace_processor/track_event/flow_events_proto_v2.out
new file mode 100644
index 0000000..a8e6334
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_proto_v2.out
@@ -0,0 +1,4 @@
+"slice_out","slice_in"
+"FlowBeginSlice","FlowEndSlice_1"
+"FlowBeginSlice","FlowStepSlice"
+"FlowStepSlice","FlowEndSlice_2"
diff --git a/test/trace_processor/track_event/flow_events_proto_v2.textproto b/test/trace_processor/track_event/flow_events_proto_v2.textproto
new file mode 100644
index 0000000..d4312da
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_proto_v2.textproto
@@ -0,0 +1,84 @@
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 1
+    thread {
+      pid: 1
+      tid: 1
+      thread_name: "t1"
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 2
+    thread {
+      pid: 2
+      tid: 2
+      thread_name: "t2"
+    }
+  }
+}
+packet {
+  timestamp: 10000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowBeginSlice"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      bind_id: 1
+      flow_direction: FLOW_OUT
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 30000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowEndSlice_1"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      bind_id: 1
+      flow_direction: FLOW_IN
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 50000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowStepSlice"
+    categories: "test"
+    track_uuid: 1
+    legacy_event {
+      bind_id: 1
+      flow_direction: FLOW_INOUT
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 55000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowEndSlice_2"
+    categories: "test"
+    track_uuid: 2
+    legacy_event {
+      bind_id: 1
+      flow_direction: FLOW_IN
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
\ No newline at end of file
diff --git a/test/trace_processor/track_event/flow_events_track_event.out b/test/trace_processor/track_event/flow_events_track_event.out
new file mode 100644
index 0000000..df28e99
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_track_event.out
@@ -0,0 +1,4 @@
+"slice_out","slice_in"
+"FlowSlice1Start","FlowSlice1End"
+"FlowSlice1Start2Start","FlowSlice1End"
+"FlowSlice1Start2Start","FlowSlice2End"
diff --git a/test/trace_processor/track_event/flow_events_track_event.textproto b/test/trace_processor/track_event/flow_events_track_event.textproto
new file mode 100644
index 0000000..fcd3788
--- /dev/null
+++ b/test/trace_processor/track_event/flow_events_track_event.textproto
@@ -0,0 +1,95 @@
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 1
+    thread {
+      pid: 1
+      tid: 1
+      thread_name: "t1"
+    }
+  }
+}
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  track_descriptor {
+    uuid: 2
+    thread {
+      pid: 2
+      tid: 2
+      thread_name: "t2"
+    }
+  }
+}
+packet {
+  timestamp: 10000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice1Start"
+    categories: "test"
+    track_uuid: 1,
+    flow_ids: 1,
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 30000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice1End"
+    categories: "test"
+    track_uuid: 2,
+    terminating_flow_ids: 1,
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 50000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice1Start2Start"
+    categories: "test"
+    track_uuid: 1,
+    flow_ids: 1,
+    flow_ids: 2,
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 55000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice1End"
+    categories: "test"
+    track_uuid: 2,
+    flow_ids: 1,
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
+packet {
+  timestamp: 59000
+  trusted_packet_sequence_id: 1
+  track_event {
+    name: "FlowSlice2End"
+    categories: "test"
+    track_uuid: 2,
+    flow_ids: 2,
+    legacy_event {
+      duration_us: 10
+      phase: 88 # 'X'
+    }
+  }
+}
diff --git a/test/trace_processor/track_event/index b/test/trace_processor/track_event/index
index a0d26df..ac081f1 100644
--- a/test/trace_processor/track_event/index
+++ b/test/trace_processor/track_event/index
@@ -24,3 +24,11 @@
 
 # Clock handling
 track_event_monotonic_trace_clock.textproto track_event_slices.sql track_event_monotonic_trace_clock_slices.out
+
+# HistogramName interning
+track_event_chrome_histogram_sample.textproto track_event_args.sql track_event_chrome_histogram_sample_args.out
+
+# Flow events importing from proto
+flow_events_track_event.textproto flow_events.sql flow_events_track_event.out
+flow_events_proto_v2.textproto flow_events.sql flow_events_proto_v2.out
+flow_events_proto_v1.textproto flow_events.sql flow_events_proto_v1.out
diff --git a/test/trace_processor/track_event/track_event_args.sql b/test/trace_processor/track_event/track_event_args.sql
index 10187a2..d1d4cc3 100644
--- a/test/trace_processor/track_event/track_event_args.sql
+++ b/test/trace_processor/track_event/track_event_args.sql
@@ -13,4 +13,4 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-select arg_set_id, flat_key, key, int_value, string_value from args order by arg_set_id, key asc;
\ No newline at end of file
+select flat_key, key, int_value, string_value from args order by arg_set_id, key asc;
\ No newline at end of file
diff --git a/test/trace_processor/track_event/track_event_chrome_histogram_sample.textproto b/test/trace_processor/track_event/track_event_chrome_histogram_sample.textproto
new file mode 100644
index 0000000..c9c9deb
--- /dev/null
+++ b/test/trace_processor/track_event/track_event_chrome_histogram_sample.textproto
@@ -0,0 +1,127 @@
+# Valid interning of Compositing.Display.DrawToSwapUs.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 10
+      sample: 100
+      name_iid: 1
+    }
+  }
+  interned_data {
+    histogram_names {
+      iid: 1
+      name: "Compositing.Display.DrawToSwapUs"
+    }
+  }
+}
+# Valid interning of CompositorLatency.TotalLatency.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 20
+      sample: 200
+      name_iid: 2
+    }
+  }
+  interned_data {
+    histogram_names {
+      iid: 2
+      name: "CompositorLatency.TotalLatency"
+    }
+  }
+}
+# Both name_iid and name are set for chrome_histogram_sample: name must not
+# change.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name: "Graphics.Smoothness.Checkerboarding.MainThreadAnimation"
+      name_hash: 30
+      sample: 300
+      name_iid: 3
+    }
+  }
+  interned_data {
+    histogram_names {
+      iid: 3
+      name: "Graphics.Smoothness.Checkerboarding.PinchZoom"
+    }
+  }
+}
+# Invalid interning of CompositorLatency.TotalLatency: wrong iid.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 40
+      sample: 400
+      name_iid: 4
+    }
+  }
+  interned_data {
+    histogram_names {
+      iid: 1
+      name: "CompositorLatency.TotalLatency"
+    }
+  }
+}
+# name_iid is not set for chrome_histogram_sample.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 50
+      sample: 500
+    }
+  }
+  interned_data {
+    histogram_names {
+      iid: 5
+      name: "CompositorLatency.TotalLatency"
+    }
+  }
+}
+# No name interning.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 0
+  incremental_state_cleared: true
+  track_event {
+    categories: "disabled-by-default-histogram_samples"
+    type: 3
+    name_iid: 1
+    chrome_histogram_sample {
+      name_hash: 60
+      sample: 600
+      name: "Memory.GPU.PeakMemoryUsage.PageLoad"
+    }
+  }
+}
diff --git a/test/trace_processor/track_event/track_event_chrome_histogram_sample_args.out b/test/trace_processor/track_event/track_event_chrome_histogram_sample_args.out
new file mode 100644
index 0000000..c2265c9
--- /dev/null
+++ b/test/trace_processor/track_event/track_event_chrome_histogram_sample_args.out
@@ -0,0 +1,24 @@
+"flat_key","key","int_value","string_value"
+"is_root_in_scope","is_root_in_scope",1,"[NULL]"
+"source","source","[NULL]","descriptor"
+"source_id","source_id",0,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","Compositing.Display.DrawToSwapUs"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",10,"[NULL]"
+"chrome_histogram_sample.name_iid","chrome_histogram_sample.name_iid",1,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",100,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","CompositorLatency.TotalLatency"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",20,"[NULL]"
+"chrome_histogram_sample.name_iid","chrome_histogram_sample.name_iid",2,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",200,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","Graphics.Smoothness.Checkerboarding.MainThreadAnimation"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",30,"[NULL]"
+"chrome_histogram_sample.name_iid","chrome_histogram_sample.name_iid",3,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",300,"[NULL]"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",40,"[NULL]"
+"chrome_histogram_sample.name_iid","chrome_histogram_sample.name_iid",4,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",400,"[NULL]"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",50,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",500,"[NULL]"
+"chrome_histogram_sample.name","chrome_histogram_sample.name","[NULL]","Memory.GPU.PeakMemoryUsage.PageLoad"
+"chrome_histogram_sample.name_hash","chrome_histogram_sample.name_hash",60,"[NULL]"
+"chrome_histogram_sample.sample","chrome_histogram_sample.sample",600,"[NULL]"
diff --git a/test/trace_processor/track_event/track_event_counters.sql b/test/trace_processor/track_event/track_event_counters.sql
index bfdce7e..ffa5d57 100644
--- a/test/trace_processor/track_event/track_event_counters.sql
+++ b/test/trace_processor/track_event/track_event_counters.sql
@@ -19,7 +19,6 @@
   thread.name as thread,
   thread_process.name as thread_process,
   counter_track.unit as unit,
-  counter_track.source_arg_set_id as track_args,
   counter.ts,
   counter.value
 from counter
diff --git a/test/trace_processor/track_event/track_event_counters.textproto b/test/trace_processor/track_event/track_event_counters.textproto
index decdd68..1049fc9 100644
--- a/test/trace_processor/track_event/track_event_counters.textproto
+++ b/test/trace_processor/track_event/track_event_counters.textproto
@@ -28,7 +28,7 @@
     uuid: 3
     process {
       pid: 5
-      process_name: "p1"
+      process_name: "Browser"
     }
   }
 }
@@ -116,7 +116,7 @@
     uuid: 3
     process {
       pid: 5
-      process_name: "p1"
+      process_name: "Browser"
     }
   }
 }
@@ -130,7 +130,7 @@
     parent_uuid: 1
     counter {
       type: 1                # COUNTER_THREAD_TIME_NS.
-      unit_multiplier: 1000  # provided in ys.
+      unit_multiplier: 1000  # provided in us.
       is_incremental: true   # use delta encoding.
     }
   }
@@ -149,6 +149,20 @@
   }
 }
 
+# Nested value that happens to be emitted at the same timestamp but different
+# thread time value.
+packet {
+  trusted_packet_sequence_id: 1
+  sequence_flags: 2  # SEQ_NEEDS_INCREMENTAL_STATE
+  timestamp: 2000
+  track_event {
+    categories: "cat"
+    name: "event3_on_t1"
+    type: 1                     # TYPE_SLICE_BEGIN.
+    extra_counter_values: 10    # Absolute: 2010
+  }
+}
+
 # End for event above.
 packet {
   trusted_packet_sequence_id: 1
@@ -156,7 +170,18 @@
   timestamp: 2200
   track_event {
     type: 2                   # TYPE_SLICE_END.
-    extra_counter_values: 10  # Absolute: 2010.
+    extra_counter_values: 10  # Absolute: 2020.
+  }
+}
+
+# End for event for "event2_on_t1".
+packet {
+  trusted_packet_sequence_id: 1
+  sequence_flags: 2  # SEQ_NEEDS_INCREMENTAL_STATE
+  timestamp: 2200
+  track_event {
+    type: 2                   # TYPE_SLICE_END.
+    extra_counter_values: 10  # Absolute: 2030.
   }
 }
 
@@ -189,12 +214,12 @@
   timestamp: 4000
   track_event {
     categories: "cat"
-    name: "event3_on_t1"
+    name: "event4_on_t1"
     type: 3                        # TYPE_INSTANT.
     extra_counter_track_uuids: 10  # "c1".
     extra_counter_track_uuids: 11  # "MySizeCounter".
-    extra_counter_values: 10       # Absolute: 2020.
-    extra_counter_values: 1024     # Absolute: 2020.
+    extra_counter_values: 10       # Absolute: 2040.
+    extra_counter_values: 1024     # Absolute: 2040.
   }
 }
 
diff --git a/test/trace_processor/track_event/track_event_counters_counters.out b/test/trace_processor/track_event/track_event_counters_counters.out
index d5cc7b2..227938c 100644
--- a/test/trace_processor/track_event/track_event_counters_counters.out
+++ b/test/trace_processor/track_event/track_event_counters_counters.out
@@ -1,13 +1,15 @@
-"counter_name","process","thread","thread_process","unit","track_args","ts","value"
-"thread_time","[NULL]","t1","p1","ns",1,1000,1000000.000000
-"thread_time","[NULL]","t1","p1","ns",1,1100,1010000.000000
-"thread_time","[NULL]","t1","p1","ns",1,2000,2000000.000000
-"thread_time","[NULL]","t1","p1","ns",1,2200,2010000.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",2,3000,1024.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",2,3100,2048.000000
-"thread_time","[NULL]","t1","p1","ns",1,4000,2020000.000000
-"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",2,4000,1024.000000
-"thread_time","[NULL]","t4","p1","[NULL]","[NULL]",4000,10000.000000
-"thread_instruction_count","[NULL]","t4","p1","[NULL]","[NULL]",4000,20.000000
-"thread_time","[NULL]","t4","p1","[NULL]","[NULL]",4100,15000.000000
-"thread_instruction_count","[NULL]","t4","p1","[NULL]","[NULL]",4100,25.000000
+"counter_name","process","thread","thread_process","unit","ts","value"
+"thread_time","[NULL]","t1","Browser","ns",1000,1000000.000000
+"thread_time","[NULL]","t1","Browser","ns",1100,1010000.000000
+"thread_time","[NULL]","t1","Browser","ns",2000,2000000.000000
+"thread_time","[NULL]","t1","Browser","ns",2000,2010000.000000
+"thread_time","[NULL]","t1","Browser","ns",2200,2020000.000000
+"thread_time","[NULL]","t1","Browser","ns",2200,2030000.000000
+"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3000,1024.000000
+"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",3100,2048.000000
+"thread_time","[NULL]","t1","Browser","ns",4000,2040000.000000
+"MySizeCounter","[NULL]","[NULL]","[NULL]","bytes",4000,1024.000000
+"thread_time","[NULL]","t4","Browser","[NULL]",4000,10000.000000
+"thread_instruction_count","[NULL]","t4","Browser","[NULL]",4000,20.000000
+"thread_time","[NULL]","t4","Browser","[NULL]",4100,15000.000000
+"thread_instruction_count","[NULL]","t4","Browser","[NULL]",4100,25.000000
diff --git a/test/trace_processor/track_event/track_event_counters_slices.out b/test/trace_processor/track_event/track_event_counters_slices.out
index f17c0b4..8514b07 100644
--- a/test/trace_processor/track_event/track_event_counters_slices.out
+++ b/test/trace_processor/track_event/track_event_counters_slices.out
@@ -1,5 +1,6 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"[NULL]","[NULL]","t1","p1",1000,100,"cat","event1_on_t1",0
-"[NULL]","[NULL]","t1","p1",2000,200,"cat","event2_on_t1",0
-"[NULL]","[NULL]","t1","p1",4000,0,"cat","event3_on_t1",0
-"[NULL]","[NULL]","t4","p1",4000,100,"cat","event1_on_t3",0
+"track","process","thread","thread_process","ts","dur","category","name"
+"[NULL]","[NULL]","t1","Browser",1000,100,"cat","event1_on_t1"
+"[NULL]","[NULL]","t1","Browser",2000,200,"cat","event2_on_t1"
+"[NULL]","[NULL]","t1","Browser",2000,200,"cat","event3_on_t1"
+"[NULL]","[NULL]","t1","Browser",4000,0,"cat","event4_on_t1"
+"[NULL]","[NULL]","t4","Browser",4000,100,"cat","event1_on_t3"
diff --git a/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out b/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
index 5cb53f9..472d06d 100644
--- a/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
+++ b/test/trace_processor/track_event/track_event_merged_debug_annotations_args.out
@@ -1,21 +1,24 @@
-"arg_set_id","flat_key","key","int_value","string_value"
-1,"source","source","[NULL]","chrome"
-1,"source_id","source_id",1234,"[NULL]"
-1,"source_id_is_process_scoped","source_id_is_process_scoped",0,"[NULL]"
-1,"source_scope","source_scope","[NULL]","cat"
-2,"debug1.key1","debug1.key1",10,"[NULL]"
-2,"debug1.key2","debug1.key2[0]",20,"[NULL]"
-2,"debug1.key2","debug1.key2[1]",21,"[NULL]"
-2,"debug1.key2","debug1.key2[2]",22,"[NULL]"
-2,"debug1.key2","debug1.key2[3]",23,"[NULL]"
-2,"debug1.key3","debug1.key3",30,"[NULL]"
-2,"debug2.key1","debug2.key1",10,"[NULL]"
-2,"debug2.key2","debug2.key2[0]",20,"[NULL]"
-2,"debug2.key2","debug2.key2[1]",21,"[NULL]"
-2,"debug2.key2","debug2.key2[2]",22,"[NULL]"
-2,"debug2.key2","debug2.key2[3]",23,"[NULL]"
-2,"debug2.key3.key31","debug2.key3.key31",31,"[NULL]"
-2,"debug2.key3.key32","debug2.key3.key32",32,"[NULL]"
-2,"debug2.key4","debug2.key4",40,"[NULL]"
-2,"debug3","debug3",32,"[NULL]"
-2,"legacy_event.passthrough_utid","legacy_event.passthrough_utid",1,"[NULL]"
+"flat_key","key","int_value","string_value"
+"is_root_in_scope","is_root_in_scope",1,"[NULL]"
+"source","source","[NULL]","descriptor"
+"source_id","source_id",1,"[NULL]"
+"source","source","[NULL]","chrome"
+"source_id","source_id",1234,"[NULL]"
+"source_id_is_process_scoped","source_id_is_process_scoped",0,"[NULL]"
+"source_scope","source_scope","[NULL]","cat"
+"debug1.key1","debug1.key1",10,"[NULL]"
+"debug1.key2","debug1.key2[0]",20,"[NULL]"
+"debug1.key2","debug1.key2[1]",21,"[NULL]"
+"debug1.key2","debug1.key2[2]",22,"[NULL]"
+"debug1.key2","debug1.key2[3]",23,"[NULL]"
+"debug1.key3","debug1.key3",30,"[NULL]"
+"debug2.key1","debug2.key1",10,"[NULL]"
+"debug2.key2","debug2.key2[0]",20,"[NULL]"
+"debug2.key2","debug2.key2[1]",21,"[NULL]"
+"debug2.key2","debug2.key2[2]",22,"[NULL]"
+"debug2.key2","debug2.key2[3]",23,"[NULL]"
+"debug2.key3.key31","debug2.key3.key31",31,"[NULL]"
+"debug2.key3.key32","debug2.key3.key32",32,"[NULL]"
+"debug2.key4","debug2.key4",40,"[NULL]"
+"debug3","debug3",32,"[NULL]"
+"legacy_event.passthrough_utid","legacy_event.passthrough_utid",1,"[NULL]"
diff --git a/test/trace_processor/track_event/track_event_monotonic_trace_clock_slices.out b/test/trace_processor/track_event/track_event_monotonic_trace_clock_slices.out
index c3726b6..55c67b8 100644
--- a/test/trace_processor/track_event/track_event_monotonic_trace_clock_slices.out
+++ b/test/trace_processor/track_event/track_event_monotonic_trace_clock_slices.out
@@ -1,3 +1,3 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"name1","[NULL]","[NULL]","[NULL]",1000,0,"cat","name1",0
-"name1","[NULL]","[NULL]","[NULL]",2000,0,"cat","name2",0
+"track","process","thread","thread_process","ts","dur","category","name"
+"name1","[NULL]","[NULL]","[NULL]",1000,0,"cat","name1"
+"name1","[NULL]","[NULL]","[NULL]",2000,0,"cat","name2"
diff --git a/test/trace_processor/track_event/track_event_same_tids_slices.out b/test/trace_processor/track_event/track_event_same_tids_slices.out
index d882f13..0812854 100644
--- a/test/trace_processor/track_event/track_event_same_tids_slices.out
+++ b/test/trace_processor/track_event/track_event_same_tids_slices.out
@@ -1,3 +1,3 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"[NULL]","[NULL]","t1","[NULL]",1000,0,"cat","name1",0
-"[NULL]","[NULL]","t2","[NULL]",2000,0,"cat","name2",0
+"track","process","thread","thread_process","ts","dur","category","name"
+"[NULL]","[NULL]","t1","[NULL]",1000,0,"cat","name1"
+"[NULL]","[NULL]","t2","[NULL]",2000,0,"cat","name2"
diff --git a/test/trace_processor/track_event/track_event_slices.sql b/test/trace_processor/track_event/track_event_slices.sql
index 5146810..8308b16 100644
--- a/test/trace_processor/track_event/track_event_slices.sql
+++ b/test/trace_processor/track_event/track_event_slices.sql
@@ -21,8 +21,7 @@
   slice.ts,
   slice.dur,
   slice.category,
-  slice.name,
-  slice.arg_set_id
+  slice.name
 from slice
 left join track on slice.track_id = track.id
 left join process_track on slice.track_id = process_track.id
diff --git a/test/trace_processor/track_event/track_event_tracks.textproto b/test/trace_processor/track_event/track_event_tracks.textproto
index a79b999..c2d3f7e 100644
--- a/test/trace_processor/track_event/track_event_tracks.textproto
+++ b/test/trace_processor/track_event/track_event_tracks.textproto
@@ -295,3 +295,25 @@
     }
   }
 }
+
+# Track descriptor without name and process/thread association derives its
+# name from the first event on the track.
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 40000
+  track_descriptor {
+    uuid: 13
+    parent_uuid: 10
+  }
+}
+
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 40000
+  track_event {
+    track_uuid: 13
+    categories: "cat"
+    name: "event_and_track_async3"
+    type: 3
+  }
+}
diff --git a/test/trace_processor/track_event/track_event_tracks_slices.out b/test/trace_processor/track_event/track_event_tracks_slices.out
index 1471f76..d0186cc 100644
--- a/test/trace_processor/track_event/track_event_tracks_slices.out
+++ b/test/trace_processor/track_event/track_event_tracks_slices.out
@@ -1,14 +1,15 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"[NULL]","[NULL]","t1","p1",1000,0,"cat","event1_on_t1",0
-"[NULL]","[NULL]","t2","p1",2000,0,"cat","event1_on_t2",0
-"[NULL]","[NULL]","t2","p1",3000,0,"cat","event2_on_t2",0
-"[NULL]","p1","[NULL]","[NULL]",4000,0,"cat","event1_on_p1",3
-"async","p1","[NULL]","[NULL]",5000,0,"cat","event1_on_async",3
-"async2","p1","[NULL]","[NULL]",5100,100,"cat","event1_on_async2",3
-"[NULL]","[NULL]","t1","p1",6000,0,"cat","event3_on_t1",0
-"[NULL]","[NULL]","t3","p1",11000,0,"cat","event1_on_t3",0
-"[NULL]","p2","[NULL]","[NULL]",21000,0,"cat","event1_on_p2",7
-"[NULL]","[NULL]","t4","p2",22000,0,"cat","event1_on_t4",0
-"Default Track","[NULL]","[NULL]","[NULL]",30000,0,"cat","event1_on_t1",0
-"[NULL]","p2","[NULL]","[NULL]",31000,0,"cat","event2_on_p2",6
-"[NULL]","[NULL]","t4","p2",32000,0,"cat","event2_on_t4",0
+"track","process","thread","thread_process","ts","dur","category","name"
+"[NULL]","[NULL]","t1","p1",1000,0,"cat","event1_on_t1"
+"[NULL]","[NULL]","t2","p1",2000,0,"cat","event1_on_t2"
+"[NULL]","[NULL]","t2","p1",3000,0,"cat","event2_on_t2"
+"[NULL]","p1","[NULL]","[NULL]",4000,0,"cat","event1_on_p1"
+"async","p1","[NULL]","[NULL]",5000,0,"cat","event1_on_async"
+"async2","p1","[NULL]","[NULL]",5100,100,"cat","event1_on_async2"
+"[NULL]","[NULL]","t1","p1",6000,0,"cat","event3_on_t1"
+"[NULL]","[NULL]","t3","p1",11000,0,"cat","event1_on_t3"
+"[NULL]","p2","[NULL]","[NULL]",21000,0,"cat","event1_on_p2"
+"[NULL]","[NULL]","t4","p2",22000,0,"cat","event1_on_t4"
+"Default Track","[NULL]","[NULL]","[NULL]",30000,0,"cat","event1_on_t1"
+"[NULL]","p2","[NULL]","[NULL]",31000,0,"cat","event2_on_p2"
+"[NULL]","[NULL]","t4","p2",32000,0,"cat","event2_on_t4"
+"event_and_track_async3","p1","[NULL]","[NULL]",40000,0,"cat","event_and_track_async3"
diff --git a/test/trace_processor/track_event/track_event_typed_args.textproto b/test/trace_processor/track_event/track_event_typed_args.textproto
index 7638126..fdbfe2b 100644
--- a/test/trace_processor/track_event/track_event_typed_args.textproto
+++ b/test/trace_processor/track_event/track_event_typed_args.textproto
@@ -13,50 +13,6 @@
 }
 packet {
   trusted_packet_sequence_id: 1
-  timestamp: 0
-  extension_descriptor {
-    extension_set {
-      file {
-        package: "perfetto.protos"
-        message_type {
-          extension {
-            name: "string_extension_for_testing"
-            extendee: ".perfetto.protos.TrackEvent"
-            number: 9900
-            type: TYPE_STRING
-            label: LABEL_OPTIONAL
-          }
-          extension {
-            name: "int_extension_for_testing"
-            extendee: ".perfetto.protos.TrackEvent"
-            number: 9901
-            type: TYPE_INT32
-            label: LABEL_REPEATED
-          }
-          extension {
-            name: "nested_message_extension_for_testing"
-            extendee: ".perfetto.protos.TrackEvent"
-            number: 9903
-            type: TYPE_MESSAGE
-            label: LABEL_OPTIONAL
-            type_name: ".perfetto.protos.TestExtensionChild"
-          }
-        }
-        message_type {
-          name: "TestExtensionChild"
-          field {
-            name: "child_field_for_testing"
-            number: 1
-            type: TYPE_STRING
-            label: LABEL_OPTIONAL
-          }
-        }
-      }
-    }
-  }
-}
-packet {
-  trusted_packet_sequence_id: 1
   timestamp: 1000
   track_event {
     track_uuid: 1
@@ -125,3 +81,47 @@
     }
   }
 }
+packet {
+  trusted_packet_sequence_id: 1
+  timestamp: 5000
+  extension_descriptor {
+    extension_set {
+      file {
+        package: "perfetto.protos"
+        message_type {
+          extension {
+            name: "string_extension_for_testing"
+            extendee: ".perfetto.protos.TrackEvent"
+            number: 9900
+            type: TYPE_STRING
+            label: LABEL_OPTIONAL
+          }
+          extension {
+            name: "int_extension_for_testing"
+            extendee: ".perfetto.protos.TrackEvent"
+            number: 9901
+            type: TYPE_INT32
+            label: LABEL_REPEATED
+          }
+          extension {
+            name: "nested_message_extension_for_testing"
+            extendee: ".perfetto.protos.TrackEvent"
+            number: 9903
+            type: TYPE_MESSAGE
+            label: LABEL_OPTIONAL
+            type_name: ".perfetto.protos.TestExtensionChild"
+          }
+        }
+        message_type {
+          name: "TestExtensionChild"
+          field {
+            name: "child_field_for_testing"
+            number: 1
+            type: TYPE_STRING
+            label: LABEL_OPTIONAL
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/test/trace_processor/track_event/track_event_typed_args_args.out b/test/trace_processor/track_event/track_event_typed_args_args.out
index 3644dd6..70411b2 100644
--- a/test/trace_processor/track_event/track_event_typed_args_args.out
+++ b/test/trace_processor/track_event/track_event_typed_args_args.out
@@ -1,14 +1,17 @@
-"arg_set_id","flat_key","key","int_value","string_value"
-1,"chrome_user_event.action","chrome_user_event.action","[NULL]","NewTab"
-2,"chrome_legacy_ipc.message_class","chrome_legacy_ipc.message_class","[NULL]","CLASS_AUTOMATION"
-2,"chrome_legacy_ipc.message_line","chrome_legacy_ipc.message_line",10,"[NULL]"
-3,"chrome_keyed_service.name","chrome_keyed_service.name","[NULL]","MediaRouter"
-4,"chrome_latency_info.component_info.component_type","chrome_latency_info.component_info[0].component_type","[NULL]","COMPONENT_INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL"
-4,"chrome_latency_info.component_info.time_us","chrome_latency_info.component_info[0].time_us",1201,"[NULL]"
-4,"chrome_latency_info.component_info.time_us","chrome_latency_info.component_info[1].time_us",928310,"[NULL]"
-4,"chrome_latency_info.is_coalesced","chrome_latency_info.is_coalesced",1,"[NULL]"
-4,"chrome_latency_info.trace_id","chrome_latency_info.trace_id",7,"[NULL]"
-4,"int_extension_for_testing","int_extension_for_testing[0]",42,"[NULL]"
-4,"int_extension_for_testing","int_extension_for_testing[1]",1337,"[NULL]"
-4,"nested_message_extension_for_testing.child_field_for_testing","nested_message_extension_for_testing.child_field_for_testing","[NULL]","nesting test"
-4,"string_extension_for_testing","string_extension_for_testing","[NULL]","an extension string!"
+"flat_key","key","int_value","string_value"
+"is_root_in_scope","is_root_in_scope",1,"[NULL]"
+"source","source","[NULL]","descriptor"
+"source_id","source_id",1,"[NULL]"
+"chrome_user_event.action","chrome_user_event.action","[NULL]","NewTab"
+"chrome_legacy_ipc.message_class","chrome_legacy_ipc.message_class","[NULL]","CLASS_AUTOMATION"
+"chrome_legacy_ipc.message_line","chrome_legacy_ipc.message_line",10,"[NULL]"
+"chrome_keyed_service.name","chrome_keyed_service.name","[NULL]","MediaRouter"
+"chrome_latency_info.component_info.component_type","chrome_latency_info.component_info[0].component_type","[NULL]","COMPONENT_INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL"
+"chrome_latency_info.component_info.time_us","chrome_latency_info.component_info[0].time_us",1201,"[NULL]"
+"chrome_latency_info.component_info.time_us","chrome_latency_info.component_info[1].time_us",928310,"[NULL]"
+"chrome_latency_info.is_coalesced","chrome_latency_info.is_coalesced",1,"[NULL]"
+"chrome_latency_info.trace_id","chrome_latency_info.trace_id",7,"[NULL]"
+"int_extension_for_testing","int_extension_for_testing[0]",42,"[NULL]"
+"int_extension_for_testing","int_extension_for_testing[1]",1337,"[NULL]"
+"nested_message_extension_for_testing.child_field_for_testing","nested_message_extension_for_testing.child_field_for_testing","[NULL]","nesting test"
+"string_extension_for_testing","string_extension_for_testing","[NULL]","an extension string!"
diff --git a/test/trace_processor/track_event/track_event_typed_args_slices.out b/test/trace_processor/track_event/track_event_typed_args_slices.out
index 84026f7..7259082 100644
--- a/test/trace_processor/track_event/track_event_typed_args_slices.out
+++ b/test/trace_processor/track_event/track_event_typed_args_slices.out
@@ -1,5 +1,5 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"[NULL]","[NULL]","t1","[NULL]",1000,0,"cat","name1",1
-"[NULL]","[NULL]","t1","[NULL]",2000,0,"cat","name2",2
-"[NULL]","[NULL]","t1","[NULL]",3000,0,"cat","name3",3
-"[NULL]","[NULL]","t1","[NULL]",4000,0,"cat","name4",4
+"track","process","thread","thread_process","ts","dur","category","name"
+"[NULL]","[NULL]","t1","[NULL]",1000,0,"cat","name1"
+"[NULL]","[NULL]","t1","[NULL]",2000,0,"cat","name2"
+"[NULL]","[NULL]","t1","[NULL]",3000,0,"cat","name3"
+"[NULL]","[NULL]","t1","[NULL]",4000,0,"cat","name4"
diff --git a/test/trace_processor/track_event/track_event_with_atrace.out b/test/trace_processor/track_event/track_event_with_atrace.out
index 5f12ba4..cb5f333 100644
--- a/test/trace_processor/track_event/track_event_with_atrace.out
+++ b/test/trace_processor/track_event/track_event_with_atrace.out
@@ -1,4 +1,4 @@
-"track","process","thread","thread_process","ts","dur","category","name","arg_set_id"
-"[NULL]","[NULL]","t1","[NULL]",10000,1000,"cat","event1",0
-"[NULL]","[NULL]","t1","[NULL]",20000,8000,"cat","event2",0
-"[NULL]","[NULL]","t1","[NULL]",21000,7000,"[NULL]","atrace",0
+"track","process","thread","thread_process","ts","dur","category","name"
+"[NULL]","[NULL]","t1","[NULL]",10000,1000,"cat","event1"
+"[NULL]","[NULL]","t1","[NULL]",20000,8000,"cat","event2"
+"[NULL]","[NULL]","t1","[NULL]",21000,7000,"[NULL]","atrace"
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index a018fd4..a8342f9 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -22,13 +22,13 @@
   testonly = true
   deps = [
     ":copy_protoc",
-    ":idle_alloc",
     "compact_reencode",
     "ftrace_proto_gen",
     "protoprofile",
   ]
   if (is_linux || is_android) {
     deps += [
+      ":idle_alloc",
       "busy_threads",
       "cpu_utilization",
       "dump_ftrace_stats",
@@ -52,11 +52,22 @@
       "../src/profiling/memory:client",
     ]
   }
+
+  executable("multithreaded_alloc") {
+    sources = [ "multithreaded_alloc.cc" ]
+    deps = [
+      "../gn:default_deps",
+      "../src/base",
+      "../src/profiling/memory:client_api_standalone",
+    ]
+  }
 }
 
-executable("idle_alloc") {
-  deps = [ "../gn:default_deps" ]
-  sources = [ "idle_alloc.cc" ]
+if (is_linux || is_android) {
+  executable("idle_alloc") {
+    deps = [ "../gn:default_deps" ]
+    sources = [ "idle_alloc.cc" ]
+  }
 }
 
 # The protoc binary can end up in out/protoc or out/gcc_like_host/protoc
diff --git a/tools/add_test_trace.sh b/tools/add_test_trace.sh
index f91551d..1eea759 100755
--- a/tools/add_test_trace.sh
+++ b/tools/add_test_trace.sh
@@ -52,12 +52,8 @@
 gsutil acl ch -u AllUsers:R gs://perfetto/$NEW_TEST_DATA
 
 echo ""
-echo "SHA1 of file $NEW_TEST_DATA is"
-if which shasum > /dev/null; then
-NEW_SHA=$(shasum /tmp/$NEW_TEST_DATA | cut -c1-40)  # Mac OS
-else
-NEW_SHA=$(sha1sum /tmp/$NEW_TEST_DATA | cut -c1-40)  # Linux
-fi
+echo "SHA-256 of file $NEW_TEST_DATA is"
+NEW_SHA=$(shasum -a 256 /tmp/$NEW_TEST_DATA | cut -c1-64)
 echo $NEW_SHA
 
 echo ""
@@ -71,7 +67,7 @@
 echo "Updating tools/install-build-deps"
 echo ""
 
-OLD_SHA=$(cat tools/install-build-deps | grep '/test-data-.*.zip' -A1 | tail -n1 | cut -c10-49)
+OLD_SHA=$(cat tools/install-build-deps | grep '/test-data-.*.zip' -A1 | tail -n1 | egrep -o '[a-f0-9]+')
 
 # Cannot easily use sed -i, it has different syntax on Linux vs Mac.
 cat tools/install-build-deps \
diff --git a/tools/busy_threads/busy_threads.cc b/tools/busy_threads/busy_threads.cc
index 88196f5..d7976ed 100644
--- a/tools/busy_threads/busy_threads.cc
+++ b/tools/busy_threads/busy_threads.cc
@@ -23,11 +23,13 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
 
 #define PERFETTO_HAVE_PTHREADS                \
   (PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
-   PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX))
+   PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE))
 
 #if PERFETTO_HAVE_PTHREADS
 #include <pthread.h>
@@ -51,12 +53,13 @@
 void PrintUsage(const char* bin_name) {
 #if PERFETTO_HAVE_PTHREADS
   PERFETTO_ELOG(
-      "Usage: %s --threads=N --period_us=N --duty_cycle=[1-100] "
+      "Usage: %s [--background] --threads=N --period_us=N --duty_cycle=[1-100] "
       "[--thread_names=N]",
       bin_name);
 #else
-  PERFETTO_ELOG("Usage: %s --threads=N --period_us=N --duty_cycle=[1-100]",
-                bin_name);
+  PERFETTO_ELOG(
+      "Usage: %s [--background] --threads=N --period_us=N --duty_cycle=[1-100]",
+      bin_name);
 #endif
 }
 
@@ -92,15 +95,17 @@
 }
 
 int BusyThreadsMain(int argc, char** argv) {
+  bool background = false;
   int64_t num_threads = -1;
   int64_t period_us = -1;
   int64_t duty_cycle = -1;
   uint32_t thread_name_count = 0;
 
-  static struct option long_options[] = {
+  static option long_options[] = {
+    {"background", no_argument, nullptr, 'd'},
     {"threads", required_argument, nullptr, 't'},
     {"period_us", required_argument, nullptr, 'p'},
-    {"duty_cycle", required_argument, nullptr, 'd'},
+    {"duty_cycle", required_argument, nullptr, 'c'},
 #if PERFETTO_HAVE_PTHREADS
     {"thread_names", required_argument, nullptr, 'r'},
 #endif
@@ -110,13 +115,16 @@
   int c;
   while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
     switch (c) {
+      case 'd':
+        background = true;
+        break;
       case 't':
         num_threads = atol(optarg);
         break;
       case 'p':
         period_us = atol(optarg);
         break;
-      case 'd':
+      case 'c':
         duty_cycle = atol(optarg);
         break;
 #if PERFETTO_HAVE_PTHREADS
@@ -134,6 +142,30 @@
     return 1;
   }
 
+  if (background) {
+    pid_t pid;
+    switch (pid = fork()) {
+      case -1:
+        PERFETTO_FATAL("fork");
+      case 0: {
+        PERFETTO_CHECK(setsid() != -1);
+        base::ignore_result(chdir("/"));
+        base::ScopedFile null = base::OpenFile("/dev/null", O_RDONLY);
+        PERFETTO_CHECK(null);
+        PERFETTO_CHECK(dup2(*null, STDIN_FILENO) != -1);
+        PERFETTO_CHECK(dup2(*null, STDOUT_FILENO) != -1);
+        PERFETTO_CHECK(dup2(*null, STDERR_FILENO) != -1);
+        // Do not accidentally close stdin/stdout/stderr.
+        if (*null <= 2)
+          null.release();
+        break;
+      }
+      default:
+        printf("%d\n", pid);
+        exit(0);
+    }
+  }
+
   int64_t busy_us =
       static_cast<int64_t>(static_cast<double>(period_us) *
                            (static_cast<double>(duty_cycle) / 100.0));
diff --git a/tools/compact_reencode/main.cc b/tools/compact_reencode/main.cc
index ae4a2c4..19d4c22 100644
--- a/tools/compact_reencode/main.cc
+++ b/tools/compact_reencode/main.cc
@@ -15,6 +15,7 @@
  */
 
 #include <stdint.h>
+#include <stdio.h>
 #include <string>
 #include <vector>
 
@@ -44,7 +45,7 @@
 namespace {
 
 void WriteToFile(const std::string& out, const char* path) {
-  PERFETTO_CHECK(!unlink(path) || errno == ENOENT);
+  PERFETTO_CHECK(!remove(path) || errno == ENOENT);
   auto out_fd = base::OpenFile(path, O_RDWR | O_CREAT, 0666);
   if (!out_fd || base::WriteAll(out_fd.get(), out.data(), out.size()) !=
                      static_cast<ssize_t>(out.size())) {
diff --git a/tools/cpu_utilization/cpu_utilization.cc b/tools/cpu_utilization/cpu_utilization.cc
index d28a7bb..9dc1ddd 100644
--- a/tools/cpu_utilization/cpu_utilization.cc
+++ b/tools/cpu_utilization/cpu_utilization.cc
@@ -27,6 +27,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 
 // Periodically prints an un-normalized cpu usage ratio (full use of a single
@@ -66,7 +67,7 @@
   int sleep_intervals = 6;
   int target_pid = -1;
 
-  static struct option long_options[] = {
+  static option long_options[] = {
       {"pid", required_argument, nullptr, 'p'},
       {"sleep-duration-us", required_argument, nullptr, 't'},
       {"sleep-intervals", required_argument, nullptr, 'n'},
diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py
index c9298b5..ade516e 100755
--- a/tools/diff_test_trace_processor.py
+++ b/tools/diff_test_trace_processor.py
@@ -146,9 +146,10 @@
 
 
 def run_all_tests(trace_processor, trace_descriptor_path,
-                  metrics_message_factory, tests, keep_input):
+                  metrics_message_factory, tests, keep_input, rebase):
   perf_data = []
   test_failure = 0
+  rebased = 0
   for test in tests:
     trace_path = test.trace_path
     expected_path = test.expected_path
@@ -236,6 +237,16 @@
           os.path.basename(test.query_path_or_metric),
           os.path.basename(trace_path)))
 
+      if rebase:
+        if result.exit_code == 0:
+          sys.stderr.write('Rebasing {}\n'.format(expected_path))
+          with open(expected_path, 'w') as f:
+            f.write(result.actual)
+          rebase += 1
+        else:
+          sys.stderr.write(
+              'Rebase failed for {} as query failed\n'.format(expected_path))
+
       test_failure += 1
     else:
       assert len(perf_lines) == 1
@@ -253,7 +264,7 @@
               perf_result.ingest_time_ns / 1000000,
               perf_result.real_time_ns / 1000000))
 
-  return test_failure, perf_data
+  return test_failure, perf_data, rebased
 
 
 def read_all_tests_from_index(index_path, query_metric_pattern, trace_pattern):
@@ -328,6 +339,10 @@
       action='store_true',
       help='Save the (generated) input pb file for debugging')
   parser.add_argument(
+      '--rebase',
+      action='store_true',
+      help='Update the expected output file with the actual result')
+  parser.add_argument(
       'trace_processor', type=str, help='location of trace processor binary')
   args = parser.parse_args()
 
@@ -341,9 +356,16 @@
     trace_descriptor_path = args.trace_descriptor
   else:
     out_path = os.path.dirname(args.trace_processor)
-    trace_protos_path = os.path.join(out_path, 'gen', 'protos', 'perfetto',
-                                     'trace')
-    trace_descriptor_path = os.path.join(trace_protos_path, 'trace.descriptor')
+
+    def find_trace_descriptor(parent):
+      trace_protos_path = os.path.join(parent, 'gen', 'protos', 'perfetto',
+                                       'trace')
+      return os.path.join(trace_protos_path, 'trace.descriptor')
+
+    trace_descriptor_path = find_trace_descriptor(out_path)
+    if not os.path.exists(trace_descriptor_path):
+      trace_descriptor_path = find_trace_descriptor(
+          os.path.join(out_path, 'gcc_like_host'))
 
   if args.metrics_descriptor:
     metrics_descriptor_path = args.metrics_descriptor
@@ -358,14 +380,18 @@
       metrics_descriptor_path)
 
   test_run_start = datetime.datetime.now()
-  test_failure, perf_data = run_all_tests(
-      args.trace_processor, trace_descriptor_path, metrics_message_factory,
-      tests, args.keep_input)
+  test_failure, perf_data, rebased = run_all_tests(args.trace_processor,
+                                                   trace_descriptor_path,
+                                                   metrics_message_factory,
+                                                   tests, args.keep_input,
+                                                   args.rebase)
   test_run_end = datetime.datetime.now()
 
   sys.stderr.write('[==========] {} tests ran. ({} ms total)\n'.format(
       len(tests), int((test_run_end - test_run_start).total_seconds() * 1000)))
   sys.stderr.write('[  PASSED  ] {} tests.\n'.format(len(tests) - test_failure))
+  if args.rebase:
+    sys.stderr.write('{} tests rebased.\n'.format(rebased))
 
   if test_failure == 0:
     if args.perf_file:
diff --git a/tools/export_power_profiles.py b/tools/export_power_profiles.py
index b3198c4..db6e7ce 100755
--- a/tools/export_power_profiles.py
+++ b/tools/export_power_profiles.py
@@ -45,7 +45,7 @@
     ]
 
   with open(sql_path, 'w') as sql_file:
-    sql_file.write('INSERT INTO power_profile VALUES\n')
+    sql_file.write('INSERT OR REPLACE INTO power_profile VALUES\n')
     sql_file.write(',\n'.join(sql_values))
     sql_file.write(';\n')
 
diff --git a/tools/ftrace_proto_gen/BUILD.gn b/tools/ftrace_proto_gen/BUILD.gn
index 1b96c30..5560e54 100644
--- a/tools/ftrace_proto_gen/BUILD.gn
+++ b/tools/ftrace_proto_gen/BUILD.gn
@@ -23,7 +23,7 @@
     "../../gn:default_deps",
     "../../gn:protobuf_full",
     "../../src/base",
-    "../../src/traced/probes/ftrace:format_parser",
+    "../../src/traced/probes/ftrace/format_parser",
   ]
 }
 
@@ -52,6 +52,6 @@
     "../../gn:default_deps",
     "../../gn:protobuf_full",
     "../../src/base",
-    "../../src/traced/probes/ftrace:format_parser",
+    "../../src/traced/probes/ftrace/format_parser",
   ]
 }
diff --git a/tools/ftrace_proto_gen/event_whitelist b/tools/ftrace_proto_gen/event_list
similarity index 97%
rename from tools/ftrace_proto_gen/event_whitelist
rename to tools/ftrace_proto_gen/event_list
index e89131a..7c198f8 100644
--- a/tools/ftrace_proto_gen/event_whitelist
+++ b/tools/ftrace_proto_gen/event_list
@@ -337,3 +337,10 @@
 gpu_mem/gpu_mem_total
 thermal/thermal_temperature
 thermal/cdev_update
+cpuhp/cpuhp_exit
+cpuhp/cpuhp_multi_enter
+cpuhp/cpuhp_enter
+cpuhp/cpuhp_latency
+fastrpc/fastrpc_dma_stat
+dpu/tracing_mark_write
+g2d/tracing_mark_write
diff --git a/tools/ftrace_proto_gen/ftrace_descriptor_gen.cc b/tools/ftrace_proto_gen/ftrace_descriptor_gen.cc
index e230a9e..13d2803 100644
--- a/tools/ftrace_proto_gen/ftrace_descriptor_gen.cc
+++ b/tools/ftrace_proto_gen/ftrace_descriptor_gen.cc
@@ -16,6 +16,9 @@
 
 #include "tools/ftrace_proto_gen/ftrace_descriptor_gen.h"
 
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+
 namespace perfetto {
 
 void GenerateFtraceDescriptors(
diff --git a/tools/ftrace_proto_gen/ftrace_descriptor_gen.h b/tools/ftrace_proto_gen/ftrace_descriptor_gen.h
index 1a2f6ab..f713ee7 100644
--- a/tools/ftrace_proto_gen/ftrace_descriptor_gen.h
+++ b/tools/ftrace_proto_gen/ftrace_descriptor_gen.h
@@ -17,12 +17,15 @@
 #ifndef TOOLS_FTRACE_PROTO_GEN_FTRACE_DESCRIPTOR_GEN_H_
 #define TOOLS_FTRACE_PROTO_GEN_FTRACE_DESCRIPTOR_GEN_H_
 
-#include <google/protobuf/descriptor.h>
-#include <google/protobuf/descriptor.pb.h>
-
 #include "perfetto/base/logging.h"
 #include "tools/ftrace_proto_gen/ftrace_proto_gen.h"
 
+namespace google {
+namespace protobuf {
+class DescriptorPool;
+}
+}  // namespace google
+
 namespace perfetto {
 
 // Uses the ftrace event descriptor file to generate a
diff --git a/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index 7ed8110..87c6f0c 100644
--- a/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -16,9 +16,6 @@
 
 #include "tools/ftrace_proto_gen/ftrace_proto_gen.h"
 
-#include <fcntl.h>
-#include <sys/wait.h>
-#include <unistd.h>
 #include <algorithm>
 #include <fstream>
 #include <regex>
@@ -59,8 +56,10 @@
 std::string EventNameToProtoFieldName(const std::string& group,
                                       const std::string& name) {
   std::string event_name = (name == "0") ? "zero" : name;
-  if (group == "sde") {
-    event_name = "sde_" + event_name;
+  // These groups have events where the name alone conflicts with an existing
+  // proto:
+  if (group == "sde" || group == "g2d" || group == "dpu") {
+    event_name = group + "_" + event_name;
   }
   return event_name;
 }
@@ -70,12 +69,12 @@
   return ToCamelCase(EventNameToProtoFieldName(group, name)) + "FtraceEvent";
 }
 
-std::vector<FtraceEventName> ReadWhitelist(const std::string& filename) {
+std::vector<FtraceEventName> ReadAllowList(const std::string& filename) {
   std::string line;
   std::vector<FtraceEventName> lines;
   std::ifstream fin(filename, std::ios::in);
   if (!fin) {
-    fprintf(stderr, "failed to open whitelist %s\n", filename.c_str());
+    fprintf(stderr, "failed to open event list %s\n", filename.c_str());
     return lines;
   }
   while (std::getline(fin, line)) {
@@ -116,7 +115,7 @@
   return true;
 }
 
-void GenerateFtraceEventProto(const std::vector<FtraceEventName>& raw_whitelist,
+void GenerateFtraceEventProto(const std::vector<FtraceEventName>& raw_eventlist,
                               const std::set<std::string>& groups,
                               std::ostream* fout) {
   *fout << kCopyrightHeader;
@@ -148,7 +147,7 @@
 )";
 
   int i = 3;
-  for (const FtraceEventName& event : raw_whitelist) {
+  for (const FtraceEventName& event : raw_eventlist) {
     if (!event.valid()) {
       *fout << "    // removed field with id " << i << ";\n";
       ++i;
@@ -221,7 +220,7 @@
   return s;
 }
 
-// This will generate the event_info.cc file for the whitelisted protos.
+// This will generate the event_info.cc file for the listed protos.
 void GenerateEventInfo(const std::vector<std::string>& events_info,
                        std::ostream* fout) {
   std::string s = kCopyrightHeader;
diff --git a/tools/ftrace_proto_gen/ftrace_proto_gen.h b/tools/ftrace_proto_gen/ftrace_proto_gen.h
index f9faed4..5d6d0e3 100644
--- a/tools/ftrace_proto_gen/ftrace_proto_gen.h
+++ b/tools/ftrace_proto_gen/ftrace_proto_gen.h
@@ -24,7 +24,7 @@
 #include <string>
 #include <vector>
 
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
 #include "tools/ftrace_proto_gen/proto_gen_utils.h"
 
 namespace perfetto {
@@ -38,8 +38,8 @@
 std::string EventNameToProtoFieldName(const std::string& group,
                                       const std::string& name);
 
-std::vector<FtraceEventName> ReadWhitelist(const std::string& filename);
-void GenerateFtraceEventProto(const std::vector<FtraceEventName>& raw_whitelist,
+std::vector<FtraceEventName> ReadAllowList(const std::string& filename);
+void GenerateFtraceEventProto(const std::vector<FtraceEventName>& raw_eventlist,
                               const std::set<std::string>& groups,
                               std::ostream* fout);
 std::string SingleEventInfo(perfetto::Proto proto,
diff --git a/tools/ftrace_proto_gen/main.cc b/tools/ftrace_proto_gen/main.cc
index d051c4a..a92505c 100644
--- a/tools/ftrace_proto_gen/main.cc
+++ b/tools/ftrace_proto_gen/main.cc
@@ -29,7 +29,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
 #include "tools/ftrace_proto_gen/ftrace_descriptor_gen.h"
 #include "tools/ftrace_proto_gen/ftrace_proto_gen.h"
 
@@ -45,15 +45,15 @@
 
 void PrintUsage(const char* bin_name) {
   fprintf(stderr,
-          "Usage: %s -w whitelist_dir -o output_dir -d proto_descriptor "
+          "Usage: %s -w event_list_path -o output_dir -d proto_descriptor "
           "[--check_only] input_dir...\n",
           bin_name);
 }
 }  // namespace
 
 int main(int argc, char** argv) {
-  static struct option long_options[] = {
-      {"whitelist_path", required_argument, nullptr, 'w'},
+  static option long_options[] = {
+      {"event_list", required_argument, nullptr, 'w'},
       {"output_dir", required_argument, nullptr, 'o'},
       {"proto_descriptor", required_argument, nullptr, 'd'},
       {"update_build_files", no_argument, nullptr, 'b'},
@@ -63,7 +63,7 @@
   int option_index;
   int c;
 
-  std::string whitelist_path;
+  std::string event_list_path;
   std::string output_dir;
   std::string proto_descriptor;
   bool update_build_files = false;
@@ -74,7 +74,7 @@
   while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
     switch (c) {
       case 'w':
-        whitelist_path = optarg;
+        event_list_path = optarg;
         break;
       case 'o':
         output_dir = optarg;
@@ -100,12 +100,12 @@
     return 1;
   }
 
-  PERFETTO_CHECK(!whitelist_path.empty());
+  PERFETTO_CHECK(!event_list_path.empty());
   PERFETTO_CHECK(!output_dir.empty());
   PERFETTO_CHECK(!proto_descriptor.empty());
 
-  std::vector<perfetto::FtraceEventName> whitelist =
-      perfetto::ReadWhitelist(whitelist_path);
+  std::vector<perfetto::FtraceEventName> event_list =
+      perfetto::ReadAllowList(event_list_path);
   std::vector<std::string> events_info;
 
   google::protobuf::DescriptorPool descriptor_pool;
@@ -127,7 +127,7 @@
   std::set<std::string> groups;
   std::multimap<std::string, const perfetto::FtraceEventName*> group_to_event;
   std::set<std::string> new_events;
-  for (const auto& event : whitelist) {
+  for (const auto& event : event_list) {
     if (!event.valid())
       continue;
     groups.emplace(event.group());
@@ -143,7 +143,7 @@
   {
     std::unique_ptr<std::ostream> out =
         ostream_factory(output_dir + "/ftrace_event.proto");
-    perfetto::GenerateFtraceEventProto(whitelist, groups, out.get());
+    perfetto::GenerateFtraceEventProto(event_list, groups, out.get());
   }
 
   for (const std::string& group : groups) {
@@ -200,7 +200,7 @@
       }
 
       uint32_t i = 0;
-      for (; it->second != &whitelist[i]; i++)
+      for (; it->second != &event_list[i]; i++)
         ;
 
       // The first id used for events in FtraceEvent proto is 3.
diff --git a/tools/ftrace_proto_gen/proto_gen_utils.cc b/tools/ftrace_proto_gen/proto_gen_utils.cc
index ba29c72..189c8f1 100644
--- a/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -16,9 +16,6 @@
 
 #include "tools/ftrace_proto_gen/proto_gen_utils.h"
 
-#include <fcntl.h>
-#include <sys/wait.h>
-#include <unistd.h>
 #include <algorithm>
 #include <fstream>
 #include <regex>
@@ -35,7 +32,7 @@
 namespace {
 
 std::string RunClangFmt(const std::string& input) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_MAC)
   const std::string platform = "mac";
 #else
   const std::string platform = "linux64";
diff --git a/tools/ftrace_proto_gen/proto_gen_utils.h b/tools/ftrace_proto_gen/proto_gen_utils.h
index 805c402..2f9b300 100644
--- a/tools/ftrace_proto_gen/proto_gen_utils.h
+++ b/tools/ftrace_proto_gen/proto_gen_utils.h
@@ -25,14 +25,14 @@
 
 #include <google/protobuf/descriptor.h>
 
-#include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/format_parser/format_parser.h"
 
 namespace perfetto {
 
 class VerifyStream : public std::ostringstream {
  public:
   VerifyStream(std::string filename);
-  virtual ~VerifyStream();
+  ~VerifyStream() override;
 
  private:
   std::string filename_;
diff --git a/tools/gen_amalgamated b/tools/gen_amalgamated
index a527b12..95e9e97 100755
--- a/tools/gen_amalgamated
+++ b/tools/gen_amalgamated
@@ -61,16 +61,16 @@
 recurse_in_header_deps = '^//protos/.*(cpp|zero)$'
 
 # Compiler flags which aren't filtered out.
-cflag_whitelist = r'^-(W.*|fno-exceptions|fPIC|std.*|fvisibility.*)$'
+cflag_allowlist = r'^-(W.*|fno-exceptions|fPIC|std.*|fvisibility.*)$'
 
 # Linker flags which aren't filtered out.
-ldflag_whitelist = r'^-()$'
+ldflag_allowlist = r'^-()$'
 
 # Libraries which are filtered out.
-lib_blacklist = r'^(c|gcc_eh)$'
+lib_denylist = r'^(c|gcc_eh)$'
 
 # Macros which aren't filtered out.
-define_whitelist = r'^(PERFETTO.*|GOOGLE_PROTOBUF.*)$'
+define_allowlist = r'^(PERFETTO.*|GOOGLE_PROTOBUF.*)$'
 
 # Includes which will be removed from the generated source.
 includes_to_remove = r'^(gtest).*$'
@@ -138,12 +138,12 @@
 """ % tool_name
 
 
-def apply_blacklist(blacklist, items):
-  return [item for item in items if not re.match(blacklist, item)]
+def apply_denylist(denylist, items):
+  return [item for item in items if not re.match(denylist, item)]
 
 
-def apply_whitelist(whitelist, items):
-  return [item for item in items if re.match(whitelist, item)]
+def apply_allowlist(allowlist, items):
+  return [item for item in items if re.match(allowlist, item)]
 
 
 def normalize_path(path):
@@ -297,7 +297,7 @@
         result.append(flag)
       else:
         result[-1] += flag
-    return apply_whitelist(cflag_whitelist, result)
+    return apply_allowlist(cflag_allowlist, result)
 
   def _add_target_flags(self, target_name):
     for target_name in self._iterate_target_and_deps(target_name):
@@ -305,10 +305,10 @@
       self.cflags.update(self._filter_cflags(target.get('cflags', [])))
       self.cflags.update(self._filter_cflags(target.get('cflags_cc', [])))
       self.ldflags.update(
-          apply_whitelist(ldflag_whitelist, target.get('ldflags', [])))
-      self.libs.update(apply_blacklist(lib_blacklist, target.get('libs', [])))
+          apply_allowlist(ldflag_allowlist, target.get('ldflags', [])))
+      self.libs.update(apply_denylist(lib_denylist, target.get('libs', [])))
       self.defines.update(
-          apply_whitelist(define_whitelist, target.get('defines', [])))
+          apply_allowlist(define_allowlist, target.get('defines', [])))
 
   def _add_target_headers(self, target_name):
     target = self.desc[target_name]
@@ -581,6 +581,13 @@
   args = parser.parse_args()
   targets = args.targets or default_targets
 
+  # The CHANGELOG mtime triggers the the perfetto_version.gen.h genrule. This is
+  # to avoid emitting a stale version information in the remote case of somebody
+  # running gen_amalgamated incrementally after having moved to another commit.
+  changelog_path = os.path.join(project_root, 'CHANGELOG')
+  assert(os.path.exists(changelog_path))
+  subprocess.check_call(['touch', '-c', changelog_path])
+
   output = args.output
   if args.check:
     output = os.path.join(tempfile.mkdtemp(), 'perfetto_amalgamated')
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 680c02e..f5a7266 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -26,6 +26,7 @@
 # libraries are also mapped to their Android equivalents -- see |builtin_deps|.
 
 import argparse
+import collections
 import json
 import os
 import re
@@ -58,11 +59,13 @@
     '//src/perfetto_cmd:trigger_perfetto',
     '//src/profiling/memory:heapprofd_client',
     '//src/profiling/memory:heapprofd_client_api',
+    '//src/profiling/memory:heapprofd_api_noop',
     '//src/profiling/memory:heapprofd',
     '//src/profiling/memory:heapprofd_standalone_client',
     '//src/profiling/perf:traced_perf',
     '//src/traced/probes:traced_probes',
     '//src/traced/service:traced',
+    '//src/trace_processor:trace_processor_shell',
     '//test/cts:perfetto_cts_deps',
     '//test/cts:perfetto_cts_jni_deps',
     '//test:perfetto_gtest_logcat_printer',
@@ -76,7 +79,6 @@
     gn_utils.HOST_TOOLCHAIN)
 
 default_targets += [
-    '//src/trace_processor:trace_processor_shell(%s)' % gn_utils.HOST_TOOLCHAIN,
     '//tools/trace_to_text:trace_to_text(%s)' % gn_utils.HOST_TOOLCHAIN,
     protozero_plugin,
     ipc_plugin,
@@ -91,15 +93,16 @@
 }
 
 target_host_supported = [
-    '//protos/perfetto/trace:perfetto_trace_protos',
     '//:libperfetto',
+    '//protos/perfetto/trace:perfetto_trace_protos',
+    '//src/trace_processor:trace_processor_shell',
 ]
 
 # All module names are prefixed with this string to avoid collisions.
 module_prefix = 'perfetto_'
 
 # Shared libraries which are directly translated to Android system equivalents.
-shared_library_whitelist = [
+shared_library_allowlist = [
     'android',
     'android.hardware.atrace@1.0',
     'android.hardware.health@2.0',
@@ -118,7 +121,7 @@
 ]
 
 # Static libraries which are directly translated to Android system equivalents.
-static_library_whitelist = [
+static_library_allowlist = [
     'statslog_perfetto',
 ]
 
@@ -129,11 +132,17 @@
 # Location of the project in the Android source tree.
 tree_path = 'external/perfetto'
 
+# Path for the protobuf sources in the standalone build.
+buildtools_protobuf_src = '//buildtools/protobuf/src'
+
+# Location of the protobuf src dir in the Android source tree.
+android_protobuf_src = 'external/protobuf/src'
+
 # Compiler flags which are passed through to the blueprint.
-cflag_whitelist = r'^-DPERFETTO.*$'
+cflag_allowlist = r'^-DPERFETTO.*$'
 
 # Compiler defines which are passed through to the blueprint.
-define_whitelist = r'^(GOOGLE_PROTO.*)|(ZLIB_.*)|(USE_MMAP)|(HAVE_HIDDEN)$'
+define_allowlist = r'^(GOOGLE_PROTO.*)|(ZLIB_.*)|(USE_MMAP)|(HAVE_HIDDEN)$'
 
 # Shared libraries which are not in PDK.
 library_not_in_pdk = {
@@ -165,7 +174,6 @@
     'heapprofd_client_api': [
         ('include_dirs', {'bionic/libc'}),
         ('static_libs', {'libasync_safe'}),
-        ('header_libs', {'bionic_libc_platform_headers'}),
         # heapprofd_client_api MUST NOT have global constructors. Because it
         # is loaded in an __attribute__((constructor)) of libc, we cannot
         # guarantee that the global constructors get run before it is used.
@@ -175,16 +183,21 @@
     'heapprofd_client': [
         ('include_dirs', {'bionic/libc'}),
         ('static_libs', {'libasync_safe'}),
-        ('header_libs', {'bionic_libc_platform_headers'}),
     ],
     'heapprofd_standalone_client': [
         ('static_libs', {'libasync_safe'}),
         ('version_script', 'src/profiling/memory/heapprofd_client_api.map.txt'),
+        ('stl', 'libc++_static'),
     ],
     'perfetto_unittests': [
         ('data', set(enumerate_data_deps())),
         ('include_dirs', {'bionic/libc/kernel'}),
     ],
+    'perfetto_integrationtests': [
+      # heapprofd_end_to_end needs root to set a system property.
+      ('require_root', True),
+      ('test_suites', {'general-tests'}),
+    ],
     'traced_probes': [
         ('required', {'libperfetto_android_internal',
                       'trigger_perfetto',
@@ -192,19 +205,24 @@
     ],
     'libperfetto_android_internal': [('static_libs', {'libhealthhalutils'}),],
     'trace_processor_shell': [
-      ('stl', 'libc++_static'),
+      ('host', {
+        'stl': 'libc++_static',
+        'strip': {'all': True},
+        'dist': {'targets': ['sdk_repo']},
+      }),
     ],
     'libperfetto_client_experimental': [
       ('apex_available', {
         '//apex_available:platform',
-        'com.android.art.debug',
-        'com.android.art.release'}),
+        'com.android.art',
+        'com.android.art.debug'}),
+      ('shared_libs', {'liblog'}),
     ],
     'perfetto_trace_protos': [
       ('apex_available', {
         '//apex_available:platform',
-        'com.android.art.debug',
-        'com.android.art.release'}),
+        'com.android.art',
+        'com.android.art.debug'}),
     ],
 }
 
@@ -217,7 +235,13 @@
 
 
 def enable_protobuf_full(module):
-  module.shared_libs.add('libprotobuf-cpp-full')
+  if module.type == 'cc_binary_host':
+    module.static_libs.add('libprotobuf-cpp-full')
+  elif module.host_supported:
+    module.host.static_libs.add('libprotobuf-cpp-full')
+    module.android.shared_libs.add('libprotobuf-cpp-full')
+  else:
+    module.shared_libs.add('libprotobuf-cpp-full')
 
 
 def enable_protobuf_lite(module):
@@ -252,18 +276,26 @@
 def enable_sqlite(module):
   if module.type == 'cc_binary_host':
     module.static_libs.add('libsqlite')
-  else:
+  elif module.host_supported:
     # Copy what the sqlite3 command line tool does.
     module.android.shared_libs.add('libsqlite')
     module.android.shared_libs.add('libandroidicu')
     module.android.shared_libs.add('liblog')
     module.android.shared_libs.add('libutils')
     module.host.static_libs.add('libsqlite')
+  else:
+    module.shared_libs.add('libsqlite')
+    module.shared_libs.add('libandroidicu')
+    module.shared_libs.add('liblog')
+    module.shared_libs.add('libutils')
 
 
 def enable_zlib(module):
   if module.type == 'cc_binary_host':
     module.static_libs.add('libz')
+  elif module.host_supported:
+    module.android.shared_libs.add('libz')
+    module.host.static_libs.add('libz')
   else:
     module.shared_libs.add('libz')
 
@@ -271,6 +303,9 @@
 def enable_uapi_headers(module):
   module.include_dirs.add('bionic/libc/kernel')
 
+def enable_bionic_libc_platform_headers_on_android(module):
+  module.header_libs.add('bionic_libc_platform_headers')
+
 
 # Android equivalents for third-party libraries that the upstream project
 # depends on.
@@ -287,6 +322,8 @@
     '//gn:sqlite': enable_sqlite,
     '//gn:zlib': enable_zlib,
     '//gn:bionic_kernel_uapi_headers' : enable_uapi_headers,
+    '//src/profiling/memory:bionic_libc_platform_headers_on_android':
+      enable_bionic_libc_platform_headers_on_android,
 }
 
 # ----------------------------------------------------------------------------
@@ -349,6 +386,9 @@
     self.static_libs = set()
     self.whole_static_libs = set()
     self.cflags = set()
+    self.dist = dict()
+    self.strip = dict()
+    self.stl = None
 
   def to_string(self, output):
     nested_out = []
@@ -356,6 +396,9 @@
     self._output_field(nested_out, 'static_libs')
     self._output_field(nested_out, 'whole_static_libs')
     self._output_field(nested_out, 'cflags')
+    self._output_field(nested_out, 'stl')
+    self._output_field(nested_out, 'dist')
+    self._output_field(nested_out, 'strip')
 
     if nested_out:
       output.append('  %s: {' % self.name)
@@ -409,6 +452,8 @@
     self.genrule_srcs = set()
     self.genrule_shared_libs = set()
     self.version_script = None
+    self.require_root = None
+    self.test_suites = set()
 
   def to_string(self, output):
     if self.comment:
@@ -439,6 +484,8 @@
     self._output_field(output, 'stl')
     self._output_field(output, 'apex_available')
     self._output_field(output, 'version_script')
+    self._output_field(output, 'require_root')
+    self._output_field(output, 'test_suites')
 
     target_out = []
     self._output_field(target_out, 'android')
@@ -473,6 +520,25 @@
     output.append('}')
     output.append('')
 
+
+  def add_android_static_lib(self, lib):
+    if self.type == 'cc_binary_host':
+      raise Exception('Adding Android static lib for host tool is unsupported')
+    elif self.host_supported:
+      self.android.static_libs.add(lib)
+    else:
+      self.static_libs.add(lib)
+
+
+  def add_android_shared_lib(self, lib):
+    if self.type == 'cc_binary_host':
+      raise Exception('Adding Android shared lib for host tool is unsupported')
+    elif self.host_supported:
+      self.android.shared_libs.add(lib)
+    else:
+      self.shared_libs.add(lib)
+
+
   def _output_field(self, output, name, sort=True):
     value = getattr(self, name)
     return write_blueprint_key_value(output, name, value, sort)
@@ -535,20 +601,64 @@
         The source_genrule module.
     """
   assert (target.type == 'proto_library')
+
+  tools = {'aprotoc'}
   cpp_out_dir = '$(genDir)/%s/' % tree_path
+  target_module_name = label_to_module_name(target.name)
+
+  # In GN builds the proto path is always relative to the output directory
+  # (out/tmp.xxx).
   cmd = ['mkdir -p %s &&' % cpp_out_dir, '$(location aprotoc)']
+  cmd += ['--proto_path=%s' % tree_path]
+
+  if buildtools_protobuf_src in target.proto_paths:
+    cmd += ['--proto_path=%s' % android_protobuf_src]
+
+  # We don't generate any targets for source_set proto modules because
+  # they will be inlined into other modules if required.
+  if target.proto_plugin == 'source_set':
+    return None
+
+  # Descriptor targets only generate a single target.
+  if target.proto_plugin == 'descriptor':
+    out = '{}.bin'.format(target_module_name)
+
+    cmd += ['--descriptor_set_out=$(out)']
+    cmd += ['$(in)']
+
+    descriptor_module = Module('genrule', target_module_name, target.name)
+    descriptor_module.cmd = ' '.join(cmd)
+    descriptor_module.out = [out]
+    descriptor_module.tools = tools
+    blueprint.add_module(descriptor_module)
+
+    # Recursively extract the .proto files of all the dependencies and
+    # add them to srcs.
+    target_queue = collections.deque([target.name])
+    seen_targets = set()
+    while target_queue:
+      dep = target_queue.popleft()
+      if dep in seen_targets:
+        continue
+      seen_targets.add(dep)
+
+      current_target = gn.get_target(dep)
+      descriptor_module.srcs.update(
+          gn_utils.label_to_path(src) for src in current_target.sources)
+      target_queue.extend(current_target.proto_deps)
+
+    return descriptor_module
 
   # We create two genrules for each proto target: one for the headers and
   # another for the sources. This is because the module that depends on the
   # generated files needs to declare two different types of dependencies --
   # source files in 'srcs' and headers in 'generated_headers' -- and it's not
   # valid to generate .h files from a source dependency and vice versa.
-  source_module_name = label_to_module_name(target.name) + '_gen'
+  source_module_name = target_module_name + '_gen'
   source_module = Module('genrule', source_module_name, target.name)
   blueprint.add_module(source_module)
   source_module.srcs.update(
       gn_utils.label_to_path(src) for src in target.sources)
-  tools = {'aprotoc'}
 
   header_module = Module('genrule', source_module_name + '_headers',
                          target.name)
@@ -565,10 +675,6 @@
   source_module.genrule_srcs.add(':' + source_module.name)
   source_module.genrule_headers.add(header_module.name)
 
-  # In GN builds the proto path is always relative to the output directory
-  # (out/tmp.xxx).
-  cmd += ['--proto_path=%s' % tree_path]
-
   if target.proto_plugin == 'proto':
     suffixes = ['pb']
     source_module.genrule_shared_libs.add('libprotobuf-cpp-lite')
@@ -611,9 +717,8 @@
 
 
 def create_merged_sql_metrics_module(blueprint, target):
-  module = Module('genrule', 'gen_merged_sql_metrics',
-                  '//src/trace_processor/metrics:gen_merged_sql_metrics')
-  module.genrule_headers.add('gen_merged_sql_metrics')
+  bp_module_name = label_to_module_name(target.name)
+  module = Module('genrule', bp_module_name, target.name)
   module.tool_files = [
       'tools/gen_merged_sql_metrics.py',
   ]
@@ -622,17 +727,55 @@
       '--cpp_out=$(out)',
       '$(in)',
   ])
+  module.genrule_headers.add(module.name)
   module.out.update(target.outputs)
   module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
   blueprint.add_module(module)
   return module
 
 
+def create_cc_proto_descriptor_module(blueprint, target):
+  bp_module_name = label_to_module_name(target.name)
+  module = Module('genrule', bp_module_name, target.name)
+  module.tool_files = [
+      'tools/gen_cc_proto_descriptor.py',
+  ]
+  module.cmd = ' '.join([
+      '$(location tools/gen_cc_proto_descriptor.py)',
+      '--gen_dir=$(genDir)',
+      '--cpp_out=$(out)',
+      '$(in)'
+  ])
+  module.genrule_headers.add(module.name)
+  module.srcs.update(
+      ':' + label_to_module_name(dep) for dep in target.proto_deps)
+  module.out.update(target.outputs)
+  blueprint.add_module(module)
+  return module
+
+
+def create_gen_version_module(blueprint, target, bp_module_name):
+  module = Module('genrule', bp_module_name, gn_utils.GEN_VERSION_TARGET)
+  script_path = gn_utils.label_to_path(target.script)
+  module.genrule_headers.add(bp_module_name)
+  module.tool_files = [ script_path ]
+  module.out.update(target.outputs)
+  module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
+  module.cmd = ' '.join([
+        'python3 $(location %s)' % script_path,
+        '--no_git',
+        '--changelog=$(location CHANGELOG)',
+        '--cpp_out=$(out)'
+  ])
+  blueprint.add_module(module)
+  return module
+
+
 def _get_cflags(target):
-  cflags = {flag for flag in target.cflags if re.match(cflag_whitelist, flag)}
+  cflags = {flag for flag in target.cflags if re.match(cflag_allowlist, flag)}
   cflags |= set("-D%s" % define
                 for define in target.defines
-                if re.match(define_whitelist, define))
+                if re.match(define_allowlist, define))
   return cflags
 
 
@@ -675,13 +818,24 @@
     return None
   elif target.type == 'proto_library':
     module = create_proto_modules(blueprint, gn, target)
-  elif target.type == 'action' and 'gen_merged_sql_metrics' in target.name:
-    module = create_merged_sql_metrics_module(blueprint, target)
+    if module is None:
+      return None
+  elif target.type == 'action':
+    if 'gen_merged_sql_metrics' in target.name:
+      module = create_merged_sql_metrics_module(blueprint, target)
+    elif re.match('.*gen_cc_.*_descriptor$', target.name):
+      module = create_cc_proto_descriptor_module(blueprint, target)
+    elif target.type == 'action' and gn_utils.label_without_toolchain(
+        target.name) == gn_utils.GEN_VERSION_TARGET:
+      module = create_gen_version_module(blueprint, target, bp_module_name)
+    else:
+      raise Error('Unhandled action: {}'.format(target.name))
   else:
     raise Error('Unknown target %s (%s)' % (target.name, target.type))
 
   blueprint.add_module(module)
-  module.host_supported = target.name in target_host_supported
+  module.host_supported = (gn_utils.label_without_toolchain(target.name) in
+                           target_host_supported)
   module.init_rc = target_initrc.get(target.name, [])
   module.srcs.update(
       gn_utils.label_to_path(src)
@@ -700,10 +854,10 @@
       # Generally library names should be mangled as 'libXXX', unless they
       # are HAL libraries (e.g., android.hardware.health@2.0).
       android_lib = lib if '@' in lib else 'lib' + lib
-      if lib in shared_library_whitelist:
-        module.shared_libs.add(android_lib)
-      if lib in static_library_whitelist:
-        module.static_libs.add(android_lib)
+      if lib in shared_library_allowlist:
+        module.add_android_shared_lib(android_lib)
+      if lib in static_library_allowlist:
+        module.add_android_static_lib(android_lib)
 
   # If the module is a static library, export all the generated headers.
   if module.type == 'cc_library_static':
@@ -716,8 +870,12 @@
       curr.update(add_val)
     elif isinstance(add_val, str) and (not curr or isinstance(curr, str)):
       setattr(module, key, add_val)
+    elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)):
+      setattr(module, key, add_val)
     elif isinstance(add_val, dict) and isinstance(curr, dict):
       curr.update(add_val)
+    elif isinstance(add_val, dict) and isinstance(curr, Target):
+      curr.__dict__.update(add_val)
     else:
       raise Error('Unimplemented type of additional_args: %r' % key)
 
@@ -842,6 +1000,13 @@
   gn = gn_utils.GnParser(desc)
   blueprint = create_blueprint_for_targets(gn, desc, args.targets or
                                            default_targets)
+
+  # TODO(primiano): enable this on Android after the TODO in
+  # perfetto_component.gni is fixed.
+  # Check for ODR violations
+  # for target_name in default_targets:
+    # checker = gn_utils.ODRChecker(gn, target_name)
+
   output = [
       """// Copyright (C) 2017 The Android Open Source Project
 //
@@ -871,6 +1036,8 @@
   out_files.append(args.output + '.swp')
   with open(out_files[-1], 'w') as f:
     f.write('\n'.join(output))
+    # Text files should have a trailing EOL.
+    f.write('\n')
 
   # Generate the perfetto_build_flags.h file.
   out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp'))
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 81f02eb..c6b7698 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -65,7 +65,7 @@
     '//test:client_api_example',
     '//src/ipc:perfetto_ipc',
     '//src/ipc/protoc_plugin:ipc_plugin',
-    '//src/protozero:libprotozero',
+    '//src/protozero:protozero',
     '//src/protozero/protoc_plugin:protozero_plugin',
     '//src/protozero/protoc_plugin:cppgen_plugin',
 ] + public_targets
@@ -82,6 +82,9 @@
     '//protos/perfetto/config:lite',
 ]
 
+# Path for the protobuf sources in the standalone build.
+buildtools_protobuf_src = '//buildtools/protobuf/src'
+
 # The directory where the generated perfetto_build_flags.h will be copied into.
 buildflags_dir = 'include/perfetto/base/build_configs/bazel'
 
@@ -100,10 +103,10 @@
         'PERFETTO_CONFIG.deps.sqlite_ext_percentile'
     ],
     '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'],
-    '//gn/standalone:gen_git_revision': [],
     '//src/trace_processor/metrics:gen_merged_sql_metrics': [[
-        ":cc_merged_sql_metrics"
-    ]]
+        ':cc_merged_sql_metrics'
+    ]],
+    gn_utils.GEN_VERSION_TARGET: ['PERFETTO_CONFIG.deps.version_header'],
 }
 
 
@@ -112,12 +115,37 @@
   label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)]
   label.outs += target.outputs
   label.cmd = r'$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)'
-  label.tools += [':gen_merged_sql_metrics_py']
+  label.exec_tools += [':gen_merged_sql_metrics_py']
+  return [label]
+
+
+def gen_version_header(target):
+  label = BazelLabel(get_bazel_label_name(target.name), 'genrule')
+  label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)]
+  label.outs += target.outputs
+  label.cmd = r'$(location gen_version_header_py)'
+  label.cmd += r' --cpp_out=$@ --changelog=$(location CHANGELOG)'
+  label.exec_tools += [':gen_version_header_py']
+  return [label]
+
+
+def gen_cc_metrics_descriptor(target):
+  label = BazelLabel(
+      get_bazel_label_name(target.name), 'perfetto_cc_proto_descriptor')
+  label.deps += [':' + get_bazel_label_name(x) for x in target.proto_deps]
+  label.outs += target.outputs
   return [label]
 
 
 custom_actions = {
+    gn_utils.GEN_VERSION_TARGET: gen_version_header,
     '//src/trace_processor/metrics:gen_merged_sql_metrics': gen_sql_metrics,
+    '//src/trace_processor/metrics:gen_cc_metrics_descriptor':
+      gen_cc_metrics_descriptor,
+    '//src/trace_processor/metrics:gen_cc_all_chrome_metrics_descriptor':
+      gen_cc_metrics_descriptor,
+    '//src/trace_processor/importers:gen_cc_config_descriptor':
+      gen_cc_metrics_descriptor,
 }
 
 # ------------------------------------------------------------------------------
@@ -141,6 +169,7 @@
     self.deps = []
     self.external_deps = []
     self.tools = []
+    self.exec_tools = []
     self.outs = []
 
   def __lt__(self, other):
@@ -155,7 +184,10 @@
     res += ('# GN target: %s\n' % self.comment) if self.comment else ''
     res += '%s(\n' % self.type
     any_deps = len(self.deps) + len(self.external_deps) > 0
-    ORD = ['name', 'srcs', 'hdrs', 'visibility', 'deps', 'outs', 'cmd', 'tools']
+    ORD = [
+      'name','srcs', 'hdrs', 'visibility', 'deps', 'outs', 'cmd', 'tools',
+      'exec_tools'
+    ]
     hasher = lambda x: sum((99,) + tuple(ord(c) for c in x))
     key_sorter = lambda kv: ORD.index(kv[0]) if kv[0] in ORD else hasher(kv[0])
     for k, v in sorted(iteritems(self.__dict__), key=key_sorter):
@@ -222,20 +254,59 @@
   """ Generates the xx_proto_library label for proto targets.
 
   Bazel requires that each protobuf-related target is modeled with two labels:
-  1. A plugin-dependent target (e.g. cc_library, cc_protozero_library) that has
-     only a dependency on 2 and does NOT refer to any .proto sources.
-  2. A plugin-agnostic target that defines only the .proto sources and their
+  1. A plugin-agnostic target that defines only the .proto sources and their
      dependencies.
+  2. A plugin-dependent target (e.g. cc_library, cc_protozero_library) that has
+     only a dependency on 1 and does NOT refer to any .proto sources.
   """
   assert (target.type == 'proto_library')
 
   def get_sources_label(target_name):
-    return re.sub('_(lite|zero|cpp|ipc)$', '',
+    return re.sub('_(lite|zero|cpp|ipc|source_set|descriptor)$', '',
                   get_bazel_label_name(target_name)) + '_protos'
 
   sources_label_name = get_sources_label(target.name)
 
   # Generates 1.
+  sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library')
+  sources_label.comment = target.name
+  assert (all(x.startswith('//') for x in target.sources))
+  assert (all(x.endswith('.proto') for x in target.sources))
+  sources_label.srcs = sorted([x[2:] for x in target.sources])  # Strip //.
+
+  deps = [
+      ':' + get_sources_label(x)
+      for x in target.proto_deps
+
+      # This is to avoid a dependency-on-self in the case where
+      # protos/perfetto/ipc:ipc depends on protos/perfetto/ipc:cpp and both
+      # targets resolve to "protos_perfetto_ipc_protos".
+      if get_sources_label(x) != sources_label_name
+  ]
+  sources_label.deps = sorted(deps)
+
+  # In Bazel, proto_paths are not a supported concept becauase strong dependency
+  # checking is enabled. Instead, we need to depend on the target which includes
+  # the proto we want to depend on.
+  # For example, we include the proto_path |buildtools_protobuf_src| because we
+  # want to depend on the "google/protobuf/descriptor.proto" proto file. This
+  # will be exposed by the |protobuf_descriptor_proto| dep.
+  if buildtools_protobuf_src in target.proto_paths:
+    sources_label.external_deps = [
+        'PERFETTO_CONFIG.deps.protobuf_descriptor_proto'
+    ]
+
+  if target.name in proto_targets:
+    sources_label.visibility = PUBLIC_VISIBILITY
+  else:
+    sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility']
+
+  # For 'source_set' plugins, we don't want to generate any plugin-dependent
+  # targets so just return the label of the proto sources only.
+  if target.proto_plugin == 'source_set':
+    return [sources_label]
+
+  # Generates 2.
   if target.proto_plugin == 'proto':
     plugin_label_type = 'perfetto_cc_proto_library'
   elif target.proto_plugin == 'protozero':
@@ -244,6 +315,8 @@
     plugin_label_type = 'perfetto_cc_protocpp_library'
   elif target.proto_plugin == 'ipc':
     plugin_label_type = 'perfetto_cc_ipc_library'
+  elif target.proto_plugin == 'descriptor':
+    plugin_label_type = 'perfetto_proto_descriptor'
   else:
     raise Error('Unknown proto plugin: %s' % target.proto_plugin)
   plugin_label_name = get_bazel_label_name(target.name)
@@ -262,30 +335,10 @@
         ':' + get_bazel_label_name(x) for x in target.proto_deps
     ]
 
-  # Generates 2.
-  sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library')
-  sources_label.comment = target.name
-  assert (all(x.startswith('//') for x in target.sources))
-  assert (all(x.endswith('.proto') for x in target.sources))
-  sources_label.srcs = sorted([x[2:] for x in target.sources])  # Strip //.
+  if target.proto_plugin == 'descriptor':
+    plugin_label.outs = [plugin_label_name + '.bin']
 
-  deps = [
-      ':' + get_sources_label(x)
-      for x in target.proto_deps
-
-      # This is to avoid a dependency-on-self in the case where
-      # protos/perfetto/ipc:ipc depends on protos/perfetto/ipc:cpp and both
-      # targets resolve to "protos_perfetto_ipc_protos".
-      if get_sources_label(x) != sources_label_name
-  ]
-  sources_label.deps = sorted(deps)
-
-  if target.name in proto_targets:
-    sources_label.visibility = PUBLIC_VISIBILITY
-  else:
-    sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility']
-
-  return [plugin_label, sources_label]
+  return [sources_label, plugin_label]
 
 
 def gen_target(gn_target):
@@ -386,12 +439,14 @@
     "perfetto_cc_binary",
     "perfetto_cc_ipc_library",
     "perfetto_cc_library",
+    "perfetto_cc_proto_descriptor",
     "perfetto_cc_proto_library",
     "perfetto_cc_protocpp_library",
     "perfetto_cc_protozero_library",
     "perfetto_java_proto_library",
     "perfetto_java_lite_proto_library",
     "perfetto_proto_library",
+    "perfetto_proto_descriptor",
     "perfetto_py_binary",
     "perfetto_py_library",
     "perfetto_gensignature_internal_only",
@@ -464,8 +519,12 @@
   res += public_str
   res += '# Content from BUILD.extras\n\n'
   res += extras
-  return res
 
+  # Check for ODR violations
+  for target_name in default_targets + proto_targets:
+    checker = gn_utils.ODRChecker(gn, target_name)
+
+  return res
 
 def main():
   parser = argparse.ArgumentParser(
diff --git a/tools/gen_binary_descriptors b/tools/gen_binary_descriptors
index 8c1a277..f1b6201 100755
--- a/tools/gen_binary_descriptors
+++ b/tools/gen_binary_descriptors
@@ -26,22 +26,28 @@
 import textwrap
 from compat import iteritems
 
-SOURCE_TARGET = {
-    'protos/perfetto/config/perfetto_config.proto':
-        'src/perfetto_cmd/perfetto_config.descriptor.h',
-    'protos/perfetto/metrics/metrics.proto':
-        'src/trace_processor/metrics/metrics.descriptor.h',
-    'src/protozero/test/example_proto/test_messages.proto':
-        'src/protozero/test/example_proto/test_messages.descriptor.h',
-    'protos/perfetto/trace/track_event/track_event.proto':
-        'src/trace_processor/importers/proto/track_event.descriptor.h',
-    'protos/perfetto/metrics/chrome/all_chrome_metrics.proto':
-        'src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h',
-    'protos/perfetto/trace_processor/trace_processor.proto':
-        'src/trace_processor/python/trace_processor/trace_processor.descriptor',
-      'protos/perfetto/metrics/metrics.proto':
-        'src/trace_processor/python/trace_processor/metrics.descriptor',
-}
+SOURCE_TARGET = [
+    (
+      'protos/perfetto/config/perfetto_config.proto',
+      'src/perfetto_cmd/perfetto_config.descriptor.h',
+    ),
+    (
+      'src/protozero/test/example_proto/test_messages.proto',
+      'src/protozero/test/example_proto/test_messages.descriptor.h'
+    ),
+    (
+      'protos/perfetto/trace/track_event/track_event.proto',
+      'src/trace_processor/importers/proto/track_event.descriptor.h'
+    ),
+    (
+      'protos/perfetto/trace_processor/trace_processor.proto',
+      'src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor'
+    ),
+    (
+      'protos/perfetto/metrics/metrics.proto',
+      'src/trace_processor/python/perfetto/trace_processor/metrics.descriptor'
+    ),
+]
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
 
@@ -211,7 +217,7 @@
   args = parser.parse_args()
 
   try:
-    for source, target in iteritems(SOURCE_TARGET):
+    for source, target in SOURCE_TARGET:
       if args.check_only:
         check(source, target)
       else:
diff --git a/tools/gen_cc_proto_descriptor.py b/tools/gen_cc_proto_descriptor.py
new file mode 100755
index 0000000..0c2dc66
--- /dev/null
+++ b/tools/gen_cc_proto_descriptor.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# 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.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+import os
+import sys
+import argparse
+import tempfile
+import subprocess
+import textwrap
+
+
+def write_cpp_header(gendir, target, descriptor_bytes):
+  _, target_name = os.path.split(target)
+
+  proto_name = target_name[:-len('.descriptor.h')].title().replace("_", "")
+  try:
+    ord(descriptor_bytes[0])
+    ordinal = ord
+  except TypeError:
+    ordinal = lambda x: x
+  binary = '{' + ', '.join(
+      '{0:#04x}'.format(ordinal(c)) for c in descriptor_bytes) + '}'
+  binary = textwrap.fill(
+      binary, width=80, initial_indent='    ', subsequent_indent='     ')
+
+  relative_target = os.path.relpath(target, gendir)
+  include_guard = relative_target.replace('\\', '_').replace('/', '_').replace(
+      '.', '_').upper() + '_'
+
+  with open(target, 'wb') as f:
+    f.write("""/*
+ * 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 {include_guard}
+#define {include_guard}
+
+#include <stddef.h>
+#include <stdint.h>
+#include <array>
+
+namespace perfetto {{
+
+constexpr std::array<uint8_t, {size}> k{proto_name}Descriptor{{
+{binary}}};
+
+}}  // namespace perfetto
+
+#endif  // {include_guard}
+""".format(
+        proto_name=proto_name,
+        size=len(descriptor_bytes),
+        binary=binary,
+        include_guard=include_guard,
+    ).encode())
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--cpp_out', required=True)
+  parser.add_argument('--gen_dir', default='')
+  parser.add_argument('descriptor')
+  args = parser.parse_args()
+
+  with open(args.descriptor, 'rb') as fdescriptor:
+    s = fdescriptor.read()
+    write_cpp_header(args.gen_dir, args.cpp_out, s)
+
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/gen_merged_sql_metrics.py b/tools/gen_merged_sql_metrics.py
index 065ff1b..eed5d8e 100755
--- a/tools/gen_merged_sql_metrics.py
+++ b/tools/gen_merged_sql_metrics.py
@@ -95,9 +95,19 @@
     for path, sql in sql_outputs.items():
       name = os.path.basename(path)
       variable = filename_to_variable(os.path.splitext(name)[0])
-      output.write(
-          '\nconst char {}[] = R"gendelimiter(\n{})gendelimiter";\n'.format(
-              variable, sql))
+      output.write('\nconst char {}[] = '.format(variable))
+      # MSVC doesn't like string literals that are individually longer than 16k.
+      # However it's still fine "if" "we" "concatenate" "many" "of" "them".
+      # This code splits the sql in string literals of ~1000 chars each.
+      line_groups = ['']
+      for line in sql.split('\n'):
+        line_groups[-1] += line + '\n'
+        if len(line_groups[-1]) > 1000:
+          line_groups.append('')
+
+      for line in line_groups:
+        output.write('R"_d3l1m1t3r_({})_d3l1m1t3r_"\n'.format(line))
+      output.write(';\n')
 
     output.write(FILE_TO_SQL_STRUCT)
 
diff --git a/tools/gn_utils.py b/tools/gn_utils.py
index da69431..45c5885 100644
--- a/tools/gn_utils.py
+++ b/tools/gn_utils.py
@@ -16,6 +16,7 @@
 # projects.
 
 from __future__ import print_function
+import collections
 import errno
 import filecmp
 import json
@@ -27,10 +28,17 @@
 from compat import iteritems
 
 BUILDFLAGS_TARGET = '//gn:gen_buildflags'
+GEN_VERSION_TARGET = '//src/base:version_gen_h'
 TARGET_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host'
 HOST_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host'
 LINKER_UNIT_TYPES = ('executable', 'shared_library', 'static_library')
 
+# TODO(primiano): investigate these, they require further componentization.
+ODR_VIOLATION_IGNORE_TARGETS = {
+    '//test/cts:perfetto_cts_deps',
+    '//:perfetto_integrationtests',
+}
+
 
 def _check_command_output(cmd, cwd):
   try:
@@ -190,6 +198,79 @@
   return res
 
 
+class ODRChecker(object):
+  """Detects ODR violations in linker units
+
+  When we turn GN source sets into Soong & Bazel file groups, there is the risk
+  to create ODR violations by including the same file group into different
+  linker unit (this is because other build systems don't have a concept
+  equivalent to GN's source_set). This class navigates the transitive
+  dependencies (mostly static libraries) of a target and detects if multiple
+  paths end up including the same file group. This is to avoid situations like:
+
+  traced.exe -> base(file group)
+  traced.exe -> libperfetto(static lib) -> base(file group)
+  """
+
+  def __init__(self, gn, target_name):
+    self.gn = gn
+    self.root = gn.get_target(target_name)
+    self.source_sets = collections.defaultdict(set)
+    self.deps_visited = set()
+    self.source_set_hdr_only = {}
+
+    self._visit(target_name)
+    num_violations = 0
+    if target_name in ODR_VIOLATION_IGNORE_TARGETS:
+      return
+    for sset, paths in self.source_sets.items():
+      if self.is_header_only(sset):
+        continue
+      if len(paths) != 1:
+        num_violations += 1
+        print(
+            'ODR violation in target %s, multiple paths include %s:\n  %s' %
+            (target_name, sset, '\n  '.join(paths)),
+            file=sys.stderr)
+    if num_violations > 0:
+      raise Exception('%d ODR violations detected. Build generation aborted' %
+                      num_violations)
+
+  def _visit(self, target_name, parent_path=''):
+    target = self.gn.get_target(target_name)
+    path = ((parent_path + ' > ') if parent_path else '') + target_name
+    if not target:
+      raise Exception('Cannot find target %s' % target_name)
+    for ssdep in target.source_set_deps:
+      name_and_path = '%s (via %s)' % (target_name, path)
+      self.source_sets[ssdep].add(name_and_path)
+    deps = set(target.deps).union(target.proto_deps) - self.deps_visited
+    for dep_name in deps:
+      dep = self.gn.get_target(dep_name)
+      if dep.type == 'executable':
+        continue  # Execs are strong boundaries and don't cause ODR violations.
+      # static_library dependencies should reset the path. It doesn't matter if
+      # we get to a source file via:
+      # source_set1 > static_lib > source.cc OR
+      # source_set1 > source_set2 > static_lib > source.cc
+      # This is NOT an ODR violation because source.cc is linked from the same
+      # static library
+      next_parent_path = path if dep.type != 'static_library' else ''
+      self.deps_visited.add(dep_name)
+      self._visit(dep_name, next_parent_path)
+
+  def is_header_only(self, source_set_name):
+    cached = self.source_set_hdr_only.get(source_set_name)
+    if cached is not None:
+      return cached
+    target = self.gn.get_target(source_set_name)
+    if target.type != 'source_set':
+      raise TypeError('%s is not a source_set' % source_set_name)
+    res = all(src.endswith('.h') for src in target.sources)
+    self.source_set_hdr_only[source_set_name] = res
+    return res
+
+
 class GnParser(object):
   """A parser with some cleverness for GN json desc files
 
@@ -224,9 +305,10 @@
       self.testonly = False
       self.toolchain = None
 
-      # Only set when type == proto_library.
+      # These are valid only for type == proto_library.
       # This is typically: 'proto', 'protozero', 'ipc'.
       self.proto_plugin = None
+      self.proto_paths = set()
 
       self.sources = set()
 
@@ -271,7 +353,7 @@
 
     def update(self, other):
       for key in ('cflags', 'defines', 'deps', 'include_dirs', 'ldflags',
-                  'source_set_deps', 'proto_deps', 'libs'):
+                  'source_set_deps', 'proto_deps', 'libs', 'proto_paths'):
         self.__dict__[key].update(other.__dict__.get(key, []))
 
   def __init__(self, gn_desc):
@@ -314,6 +396,7 @@
       self.proto_libs[target.name] = target
       target.type = 'proto_library'
       target.proto_plugin = proto_target_type
+      target.proto_paths.update(self.get_proto_paths(proto_desc))
       target.sources.update(proto_desc.get('sources', []))
       assert (all(x.endswith('.proto') for x in target.sources))
     elif target.type == 'source_set':
@@ -324,7 +407,8 @@
       target.sources.update(desc.get('sources', []))
     elif target.type == 'action':
       self.actions[gn_target_name] = target
-      target.inputs.update(desc['inputs'])
+      target.inputs.update(desc.get('inputs', []))
+      target.sources.update(desc.get('sources', []))
       outs = [re.sub('^//out/.+?/gen/', '', x) for x in desc['outputs']]
       target.outputs.update(outs)
       target.script = desc['script']
@@ -345,7 +429,11 @@
         target.deps.add(dep_name)
       elif dep.type == 'proto_library':
         target.proto_deps.add(dep_name)
-        target.proto_deps.update(dep.proto_deps)  # Bubble up deps.
+        target.proto_paths.update(dep.proto_paths)
+
+        # Don't bubble deps for action targets
+        if target.type != 'action':
+          target.proto_deps.update(dep.proto_deps)  # Bubble up deps.
       elif dep.type == 'source_set':
         target.source_set_deps.add(dep_name)
         target.update(dep)  # Bubble up source set's cflags/ldflags etc.
@@ -359,18 +447,51 @@
 
     return target
 
+  def get_proto_paths(self, proto_desc):
+    # import_dirs in metadata will be available for source_set targets.
+    metadata = proto_desc.get('metadata', {})
+    import_dirs = metadata.get('import_dirs', [])
+    if import_dirs:
+      return import_dirs
+
+    # For all non-source-set targets, we need to parse the command line
+    # of the protoc invocation.
+    proto_paths = []
+    args = proto_desc.get('args', [])
+    for i, arg in enumerate(args):
+      if arg != '--proto_path':
+        continue
+      proto_paths.append(re.sub('^../../', '//', args[i + 1]))
+    return proto_paths
+
   def get_proto_target_type_(self, target):
     """ Checks if the target is a proto library and return the plugin.
 
         Returns:
             (None, None): if the target is not a proto library.
-            (plugin, gen_desc) where |plugin| is 'proto' in the default (lite)
-            case or 'protozero' or 'ipc'; |gen_desc| is the GN json descriptor
-            of the _gen target (the one with .proto sources).
+            (plugin, proto_desc) where |plugin| is 'proto' in the default (lite)
+            case or 'protozero' or 'ipc' or 'descriptor'; |proto_desc| is the GN
+            json desc of the target with the .proto sources (_gen target for
+            non-descriptor types or the target itself for descriptor type).
         """
     parts = target.name.split('(', 1)
     name = parts[0]
     toolchain = '(' + parts[1] if len(parts) > 1 else ''
+
+    # Descriptor targets don't have a _gen target; instead we look for the
+    # characteristic flag in the args of the target itself.
+    desc = self.gn_desc_.get(target.name)
+    if '--descriptor_set_out' in desc.get('args', []):
+      return 'descriptor', desc
+
+    # Source set proto targets have a non-empty proto_library_sources in the
+    # metadata of the descirption.
+    metadata = desc.get('metadata', {})
+    if 'proto_library_sources' in metadata:
+      return 'source_set', desc
+
+    # In all other cases, we want to look at the _gen target as that has the
+    # important information.
     gen_desc = self.gn_desc_.get('%s_gen%s' % (name, toolchain))
     if gen_desc is None or gen_desc['type'] != 'action':
       return None, None
diff --git a/tools/heap_profile b/tools/heap_profile
index 918eee4..4880702 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -32,8 +32,8 @@
 
 
 TRACE_TO_TEXT_SHAS = {
-    'linux': 'a1e1e0b447721ea11285784355a834515caa1ea2',
-    'mac': '1af18304a16a7b35e9c30c718bdfab4732d9abfc',
+    'linux': '0df4f571099135a29659caf35fd5e343ef59bdc3',
+    'mac': '71b86dfd10409fd9ad9c41cc06cd8533bd2ffa85',
 }
 TRACE_TO_TEXT_PATH = tempfile.gettempdir()
 TRACE_TO_TEXT_BASE_URL = ('https://storage.googleapis.com/perfetto/')
@@ -44,13 +44,17 @@
     'stderr': NULL,
 }
 
-UUID = str(uuid.uuid4())
+UUID = str(uuid.uuid4())[-6:]
 
 def check_hash(file_name, sha_value):
+  file_hash = hashlib.sha1()
   with open(file_name, 'rb') as fd:
-    # TODO(fmayer): Chunking.
-    file_hash = hashlib.sha1(fd.read()).hexdigest()
-    return file_hash == sha_value
+    while True:
+      chunk = fd.read(4096)
+      if not chunk:
+        break
+      file_hash.update(chunk)
+    return file_hash.hexdigest() == sha_value
 
 
 def load_trace_to_text(platform):
@@ -89,11 +93,9 @@
   config {{
     name: "android.heapprofd"
     heapprofd_config {{
-
       shmem_size_bytes: {shmem_size}
       sampling_interval_bytes: {interval}
 {target_cfg}
-{continuous_dump_cfg}
     }}
   }}
 }}
@@ -113,7 +115,7 @@
       }}
 """
 
-PROFILE_LOCAL_PATH = '/tmp/profile-' + UUID
+PROFILE_LOCAL_PATH = os.path.join(tempfile.gettempdir(), UUID)
 
 IS_INTERRUPTED = False
 
@@ -129,6 +131,23 @@
     "https://perfetto.dev/docs/data-sources/native-heap-profiler#troubleshooting.",
     file=sys.stderr)
 
+def known_issues_url(number):
+  return ('https://perfetto.dev/docs/data-sources/native-heap-profiler'
+          '#known-issues-android{}'.format(number))
+
+KNOWN_ISSUES = {
+  '10': known_issues_url(10),
+  'Q': known_issues_url(10),
+  '11': known_issues_url(11),
+  'R': known_issues_url(11),
+}
+
+def maybe_known_issues():
+  release_or_codename = subprocess.check_output(
+    ['adb', 'shell', 'getprop', 'ro.build.version.release_or_codename']
+  ).decode('utf-8').strip()
+  return KNOWN_ISSUES.get(release_or_codename, None)
+
 SDK = {
     'R': 30,
 }
@@ -156,10 +175,10 @@
   parser.add_argument(
       "-d",
       "--duration",
-      help="Duration of profile (ms). "
-      "Default 7 days.",
+      help="Duration of profile (ms). 0 to run until interrupted. "
+      "Default: until interrupted by user.",
       type=int,
-      default=604800000)
+      default=0)
   # This flag is a no-op now. We never start heapprofd explicitly using system
   # properties.
   parser.add_argument(
@@ -188,6 +207,17 @@
       "Requires Android 12.",
       metavar="HEAPS")
   parser.add_argument(
+      "--all-heaps",
+      action="store_true",
+      help="Collect allocations from all heaps registered by target."
+  )
+  parser.add_argument(
+      "--no-android-tree-symbolization",
+      action="store_true",
+      help="Do not symbolize using currently lunched target in the "
+      "Android tree."
+  )
+  parser.add_argument(
       "--disable-selinux",
       action="store_true",
       help="Disable SELinux enforcement for duration of "
@@ -291,19 +321,21 @@
 
   target_cfg = ""
   if not args.no_block_client:
-    target_cfg += "block_client: true\n"
+    target_cfg += CFG_INDENT + "block_client: true\n"
   if args.block_client_timeout:
-    target_cfg += "block_client_timeout_us: %s\n" % args.block_client_timeout
-  if args.idle_allocations:
-    target_cfg += "idle_allocations: true\n"
+    target_cfg += (
+      CFG_INDENT + "block_client_timeout_us: %s\n" % args.block_client_timeout
+    )
   if args.no_startup:
-    target_cfg += "no_startup: true\n"
+    target_cfg += CFG_INDENT + "no_startup: true\n"
   if args.no_running:
-    target_cfg += "no_running: true\n"
+    target_cfg += CFG_INDENT + "no_running: true\n"
   if args.dump_at_max:
-    target_cfg += "dump_at_max: true\n"
+    target_cfg += CFG_INDENT + "dump_at_max: true\n"
   if args.disable_fork_teardown:
-    target_cfg += "disable_fork_teardown: true\n"
+    target_cfg += CFG_INDENT + "disable_fork_teardown: true\n"
+  if args.all_heaps:
+    target_cfg += CFG_INDENT + "all_heaps: true\n"
   if args.pid:
     for pid in args.pid.split(','):
       try:
@@ -311,40 +343,26 @@
       except ValueError:
         print("FATAL: invalid PID %s" % pid, file=sys.stderr)
         fail = True
-      target_cfg += '{}pid: {}\n'.format(CFG_INDENT, pid)
+      target_cfg += CFG_INDENT + 'pid: {}\n'.format(pid)
   if args.name:
     for name in args.name.split(','):
-      target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_INDENT, name)
+      target_cfg += CFG_INDENT + 'process_cmdline: "{}"\n'.format(name)
   if args.heaps:
     for heap in args.heaps.split(','):
-      target_cfg += '{}heaps: "{}"\n'.format(CFG_INDENT, heap)
+      target_cfg += CFG_INDENT + 'heaps: "{}"\n'.format(heap)
 
   if fail:
     parser.print_help()
     return 1
 
   trace_to_text_binary = args.trace_to_text_binary
-  if trace_to_text_binary is None:
-    platform = None
-    if sys.platform.startswith('linux'):
-      platform = 'linux'
-    elif sys.platform.startswith('darwin'):
-      platform = 'mac'
-    else:
-      print("Invalid platform: {}".format(sys.platform), file=sys.stderr)
-      return 1
 
-    trace_to_text_binary = load_trace_to_text(platform)
-
-  continuous_dump_cfg = ""
   if args.continuous_dump:
-    continuous_dump_cfg = CONTINUOUS_DUMP.format(
-        dump_interval=args.continuous_dump)
+    target_cfg += CONTINUOUS_DUMP.format(dump_interval=args.continuous_dump)
   cfg = CFG.format(
       interval=args.interval,
       duration=args.duration,
       target_cfg=target_cfg,
-      continuous_dump_cfg=continuous_dump_cfg,
       shmem_size=args.shmem_size)
   if not args.no_versions:
     cfg += PACKAGES_LIST_CFG
@@ -353,6 +371,29 @@
     print(cfg)
     return 0
 
+  # Do this AFTER print_config so we do not download trace_to_text only to
+  # print out the config.
+  has_trace_to_text = True
+  if trace_to_text_binary is None:
+    platform = None
+    if sys.platform.startswith('linux'):
+      platform = 'linux'
+    elif sys.platform.startswith('darwin'):
+      platform = 'mac'
+    elif sys.platform.startswith('win32'):
+      has_trace_to_text = False
+    else:
+      print("Invalid platform: {}".format(sys.platform), file=sys.stderr)
+      return 1
+
+    if has_trace_to_text:
+      trace_to_text_binary = load_trace_to_text(platform)
+
+  known_issues = maybe_known_issues()
+  if known_issues:
+    print('If you are experiencing problems, please see the known issues for '
+          'your release: {}.'.format(known_issues))
+
   # TODO(fmayer): Maybe feature detect whether we can remove traces instead of
   # this.
   uuid_trace = release_or_newer('R')
@@ -417,6 +458,7 @@
         ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)], **NOOUT) == 0
     device_connected = subprocess.call(['adb', 'shell', 'true'], **NOOUT) == 0
     time.sleep(1)
+  print("Waiting for profiler shutdown...")
   signal.signal(signal.SIGINT, old_handler)
   if IS_INTERRUPTED:
     # Not check_call because it could have existed in the meantime.
@@ -428,8 +470,9 @@
         ['adb', 'shell', '[ -f /proc/$(pidof simpleperf)/exe ]'], **NOOUT) == 0:
       time.sleep(1)
     subprocess.check_call(
-        ['adb', 'pull', '/data/local/tmp/heapprofd_profile', '/tmp'])
-    print("Pulled simpleperf profile to /tmp/heapprofd_profile")
+        ['adb', 'pull', '/data/local/tmp/heapprofd_profile', profile_target])
+    print(
+      "Pulled simpleperf profile to " + profile_target + "/heapprofd_profile")
 
   # Wait for perfetto cmd to return.
   while exists:
@@ -437,29 +480,86 @@
         ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
     time.sleep(1)
 
-  subprocess.check_call([
-      'adb', 'pull', profile_device_path,
-      os.path.join(profile_target, 'raw-trace')
-  ], stdout=NULL)
+  profile_host_path = os.path.join(profile_target, 'raw-trace')
+  subprocess.check_call(
+    ['adb', 'pull', profile_device_path, profile_host_path], stdout=NULL)
   if uuid_trace:
     subprocess.check_call(
           ['adb', 'shell', 'rm', profile_device_path], stdout=NULL)
 
-  trace_to_text_output = subprocess.check_output(
-      [trace_to_text_binary, 'profile',
+  if not has_trace_to_text:
+    print('Wrote profile to {}'.format(profile_host_path))
+    print('This file can be opened using the Perfetto UI, https://ui.perfetto.dev')
+    return 0
+
+  binary_path = os.getenv('PERFETTO_BINARY_PATH')
+  if not args.no_android_tree_symbolization:
+    product_out = os.getenv('ANDROID_PRODUCT_OUT')
+    if product_out:
+      product_out_symbols = product_out + '/symbols'
+    else:
+      product_out_symbols = None
+
+    if binary_path is None:
+      binary_path = product_out_symbols
+    elif product_out_symbols is not None:
+      binary_path += ":" + product_out_symbols
+
+  trace_file = os.path.join(profile_target, 'raw-trace')
+  concat_files = [trace_file]
+
+  if binary_path is not None:
+    with open(os.path.join(profile_target, 'symbols'), 'w') as fd:
+      ret = subprocess.call([
+          trace_to_text_binary, 'symbolize',
           os.path.join(profile_target, 'raw-trace')],
-      env=os.environ)
+          env=dict(os.environ, PERFETTO_BINARY_PATH=binary_path),
+          stdout=fd)
+    if ret == 0:
+      concat_files.append(os.path.join(profile_target, 'symbols'))
+    else:
+      print("Failed to symbolize. Continuing without symbols.",
+      file=sys.stderr)
+
+  proguard_map = os.getenv('PERFETTO_PROGUARD_MAP')
+  if proguard_map is not None:
+    with open(os.path.join(profile_target, 'deobfuscation-packets'), 'w') as fd:
+      ret = subprocess.call([
+          trace_to_text_binary, 'deobfuscate',
+          os.path.join(profile_target, 'raw-trace')],
+          env=dict(os.environ, PERFETTO_PROGUARD_MAP=proguard_map),
+          stdout=fd)
+    if ret == 0:
+      concat_files.append(
+        os.path.join(profile_target, 'deobfuscation-packets'))
+    else:
+      print("Failed to deobfuscate. Continuing without deobfuscated.",
+      file=sys.stderr)
+
+  if len(concat_files) > 1:
+    with open(os.path.join(profile_target, 'symbolized-trace'), 'w') as out:
+      for fn in concat_files:
+        with open(fn, 'r') as inp:
+          while True:
+            buf = inp.read(4096)
+            if not buf:
+              break
+            out.write(buf)
+    trace_file = os.path.join(profile_target, 'symbolized-trace')
+
+  trace_to_text_output = subprocess.check_output(
+      [trace_to_text_binary, 'profile', trace_file])
   profile_path = None
   for word in trace_to_text_output.decode('utf-8').split():
     if 'heap_profile-' in word:
       profile_path = word
   if profile_path is None:
-    print_no_profile_error();
+    print_no_profile_error()
     return 1
 
   profile_files = os.listdir(profile_path)
   if not profile_files:
-    print_no_profile_error();
+    print_no_profile_error()
     return 1
 
   for profile_file in profile_files:
@@ -471,23 +571,11 @@
 
   symlink_path = None
   if args.output is None:
-      symlink_path = os.path.join(
-          os.path.dirname(profile_target), "heap_profile-latest")
-      if os.path.lexists(symlink_path):
-        os.unlink(symlink_path)
-      os.symlink(profile_target, symlink_path)
-
-  binary_path = os.getenv('PERFETTO_BINARY_PATH')
-  if binary_path is not None:
-      with open(os.path.join(profile_path, 'symbols'), 'w') as fd:
-          ret = subprocess.call([
-              trace_to_text_binary, 'symbolize',
-              os.path.join(profile_target, 'raw-trace')],
-              env=os.environ,
-              stdout=fd)
-          if ret != 0:
-              print("Failed to symbolize. Continuing without symbols.",
-                    file=sys.stderr)
+    symlink_path = os.path.join(
+      os.path.dirname(profile_target), "heap_profile-latest")
+    if os.path.lexists(symlink_path):
+      os.unlink(symlink_path)
+    os.symlink(profile_target, symlink_path)
 
   if symlink_path is not None:
     print("Wrote profiles to {} (symlink {})".format(
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 5dff4ab..961346f 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -19,6 +19,7 @@
 import os
 import shutil
 import subprocess
+import stat
 import sys
 import tempfile
 import zipfile
@@ -26,136 +27,206 @@
 from collections import namedtuple
 from platform import system
 
-
 # The format for the deps below is the following:
 # (target_folder, source_url, sha1, target_platform)
 # |source_url| can be either a git repo or a http url.
-# If a git repo, |sha1| is the committish that will be checked out.
-# If a http url, |sha1| is the shasum of the original file.
+# If a git repo, |checksum| is the SHA1 committish that will be checked out.
+# If a http url, |checksum| is the SHA256 of the downloaded file.
 # If the url is a .zip or .tgz file it will be automatically deflated under
 # |target_folder|, taking care of stripping the root folder if it's a single
 # root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
 # instead just buildtools/protobuf).
 # |target_platform| is either 'darwin', 'linux' or 'all' and applies the dep
 # only on the given platform
-Dependency = namedtuple('Dependency', ['target_folder', 'source_url',
-                                   'sha1', 'target_platform'])
+Dependency = namedtuple(
+    'Dependency',
+    ['target_folder', 'source_url', 'checksum', 'target_platform'])
 
 # Dependencies required to build code on the host or when targeting desktop OS.
 BUILD_DEPS_HOST = [
-    # GN
-    Dependency('buildtools/mac/gn',
-     'https://storage.googleapis.com/perfetto/gn-mac-1695-83dad00a',
-     '4c0d45772aea4146699772165e8112fa76ceb295', 'darwin'),
-    Dependency('buildtools/linux64/gn',
-     'https://storage.googleapis.com/perfetto/gn-linux64-1695-83dad00a',
-     'fcabfc379bccaa65b4e2fc791594ba124dafc7d0', 'linux'),
+    # GN. From https://chrome-infra-packages.appspot.com/dl/gn/gn/.
+    # git_revision:83dad00afb232d7235dd70dff1ee90292d72a01e .
+    Dependency(
+        'buildtools/mac/gn',
+        'https://storage.googleapis.com/perfetto/gn-mac-1695-83dad00a',
+        '513d3adeb56b745e62af4e3ccb76b76f023c6aaa25d6a2be9a89e44cd10a4c1a',
+        'darwin'),
+    Dependency(
+        'buildtools/linux64/gn',
+        'https://storage.googleapis.com/perfetto/gn-linux64-1695-83dad00a',
+        '4f589364153f182b05cd845e93407489d6ce8acc03290c897928a7bd22b20cce',
+        'linux'),
+    Dependency(
+        'buildtools/win/gn.exe',
+        'https://storage.googleapis.com/perfetto/gn-win-1695-83dad00a',
+        '908c29556539292203d2952ebf55df03697cbc7cf526a3e295f31ba2576e4cac',
+        'windows'),
 
     # clang-format
-    Dependency('buildtools/mac/clang-format',
-     'https://storage.googleapis.com/chromium-clang-format/025ca7c75f37ef4a40f3a67d81ddd11d7d0cdb9b',
-     '025ca7c75f37ef4a40f3a67d81ddd11d7d0cdb9b', 'darwin'),
-    Dependency('buildtools/linux64/clang-format',
-     'https://storage.googleapis.com/chromium-clang-format/942fc8b1789144b8071d3fc03ff0fcbe1cf81ac8',
-     '942fc8b1789144b8071d3fc03ff0fcbe1cf81ac8', 'linux'),
+    # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/mac/clang-format.sha1
+    Dependency(
+        'buildtools/mac/clang-format',
+        'https://storage.googleapis.com/chromium-clang-format/62bde1baa7196ad9df969fc1f06b66360b1a927b',
+        '6df686a937443cbe6efc013467a7ba5f98d3f187eb7765bb7abc6ce47626cf66',
+        'darwin'),
+    # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/linux64/clang-format.sha1
+    Dependency(
+        'buildtools/linux64/clang-format',
+        'https://storage.googleapis.com/chromium-clang-format/1baf0089e895c989a311b6a38ed94d0e8be4c0a7',
+        'd02a97a87e8c28898033aaf5986967b24dc47ebd5b376e1cd93e5009f22cd75e',
+        'linux'),
+    # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/win/clang-format.exe.sha1
+    Dependency(
+        'buildtools/win/clang-format.exe',
+        'https://storage.googleapis.com/chromium-clang-format/d4afd4eba27022f5f6d518133aebde57281677c9',
+        '2ba1b4d3ade90ea80316890b598ab5fc16777572be26afec6ce23117da121b80',
+        'windows'),
+
     # Keep the SHA1 in sync with |clang_format_rev| in chromium //buildtools/DEPS.
-    Dependency('buildtools/clang_format/script',
-     'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git',
-     '96636aa0e9f047f17447f2d45a094d0b59ed7917', 'all'),
+    Dependency(
+        'buildtools/clang_format/script',
+        'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git',
+        '96636aa0e9f047f17447f2d45a094d0b59ed7917', 'all'),
 
     # Ninja
-    Dependency('buildtools/mac/ninja',
-     'https://storage.googleapis.com/perfetto/ninja-mac-c15b0698da038b2bd2e8970c14c75fadc06b1add',
-     'c15b0698da038b2bd2e8970c14c75fadc06b1add', 'darwin'),
-    Dependency('buildtools/linux64/ninja',
-     'https://storage.googleapis.com/perfetto/ninja-linux64-c866952bda50c29a669222477309287119bbb7e8',
-     'c866952bda50c29a669222477309287119bbb7e8', 'linux'),
+    Dependency(
+        'buildtools/mac/ninja',
+        'https://storage.googleapis.com/perfetto/ninja-mac-c15b0698da038b2bd2e8970c14c75fadc06b1add',
+        '4224b90734590b0148ad8ee63ee7b295e88e0652e4d1f4271ef2b91d880b0e19',
+        'darwin'),
+    Dependency(
+        'buildtools/linux64/ninja',
+        'https://storage.googleapis.com/perfetto/ninja-linux64-c866952bda50c29a669222477309287119bbb7e8',
+        '54ac6a01362190aaabf4cf276f9c8982cdf11b225438940fdde3339be0f2ecdc',
+        'linux'),
+    Dependency(
+        'buildtools/win/ninja.exe',
+        'https://storage.googleapis.com/perfetto/ninja-win-4a5f05c24afef05ef03329a1bbfedee0678b524a',
+        '6f8af488be74ed8787d04e107080d05330587a4198ba047bd5b7f5b0c3150d61',
+        'windows'),
 
     # Keep in sync with Android's //external/googletest/README.version.
-    Dependency('buildtools/googletest.zip',
-     'https://github.com/google/googletest/archive/3f05f651ae3621db58468153e32016bc1397800b.zip',
-     '86384688f7c533ad325a505efc917e0cdf39a0ce', 'all'),
+    Dependency(
+        'buildtools/googletest',
+        'https://android.googlesource.com/platform/external/googletest.git',
+        '3f05f651ae3621db58468153e32016bc1397800b', 'all'),
 
     # Keep in sync with Chromium's //third_party/protobuf.
-    Dependency('buildtools/protobuf.zip',
-     'https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protobuf-cpp-3.9.0.zip',
-     'c975536dffe9d9a3d362928aef4fb9f199012b98', 'all'),
+    Dependency(
+        'buildtools/protobuf',
+        'https://chromium.googlesource.com/external/github.com/google/protobuf.git',
+        '6a59a2ad1f61d9696092f79b6d74368b4d7970a3',  # refs/tags/v3.9.0
+        'all'),
 
     # libc++, libc++abi and libunwind for Linux where we need to rebuild the C++
     # lib from sources. Keep the SHA1s in sync with Chrome's src/buildtools/DEPS.
-    Dependency('buildtools/libcxx',
-     'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git',
-     '78d6a7767ed57b50122a161b91f59f19c9bd0d19', 'all'),
-    Dependency('buildtools/libcxxabi',
-     'https://chromium.googlesource.com/chromium/llvm-project/libcxxabi.git',
-     '0d529660e32d77d9111912d73f2c74fc5fa2a858', 'all'),
-    Dependency('buildtools/libunwind',
-     'https://chromium.googlesource.com/external/llvm.org/libunwind.git',
-     '69d9b84cca8354117b9fe9705a4430d789ee599b', 'all'),
+    Dependency(
+        'buildtools/libcxx',
+        'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git',
+        'd9040c75cfea5928c804ab7c235fed06a63f743a', 'all'),
+    Dependency(
+        'buildtools/libcxxabi',
+        'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git',
+        '196ba1aaa8ac285d94f4ea8d9836390a45360533', 'all'),
+    Dependency(
+        'buildtools/libunwind',
+        'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git',
+        'd999d54f4bca789543a2eb6c995af2d9b5a1f3ed', 'all'),
 
     # Keep the revision in sync with Chrome's PACKAGE_VERSION in
     # tools/clang/scripts/update.py.
-    Dependency('buildtools/clang.tgz',
-     'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-n332890-c2443155-2.tgz',
-     'd6501ffdb5dbb0ffe8a4b873cc092a9929e661ec', 'linux'),
+    Dependency(
+        'buildtools/linux64/clang.tgz',
+        'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-llvmorg-12-init-5035-gd0abc757-3.tgz',
+        'b0c3015209b6d624844ad230064eb5c9b4429a2eafd4854981e73217c563d93d',
+        'linux'),
+    Dependency(
+        'buildtools/win/clang.tgz',
+        'https://commondatastorage.googleapis.com/chromium-browser-clang/Win/clang-llvmorg-12-init-5035-gd0abc757-3.tgz',
+        'b2854d871a466e3a060469b5edb24ca355ef64576d38778f64acbd3c6d7cf530',
+        'windows'),
 
     # Keep in sync with chromium DEPS.
-    Dependency('buildtools/libfuzzer',
-     'https://chromium.googlesource.com/chromium/llvm-project/compiler-rt/lib/fuzzer.git',
-     'debe7d2d1982e540fbd6bd78604bf001753f9e74', 'linux'),
+    Dependency(
+        'buildtools/libfuzzer',
+        'https://chromium.googlesource.com/chromium/llvm-project/compiler-rt/lib/fuzzer.git',
+        'debe7d2d1982e540fbd6bd78604bf001753f9e74', 'linux'),
 
     # Benchmarking tool.
-    Dependency('buildtools/benchmark.zip',
-     'https://github.com/google/benchmark/archive/v1.5.0.zip',
-     'a9c9bd8a28db82f5ba02998197cfcc4db5a67507', 'all'),
+    Dependency(
+        'buildtools/benchmark',
+        'https://chromium.googlesource.com/external/github.com/google/benchmark.git',
+        '090faecb454fbd6e6e17a75ef8146acb037118d4', 'all'),
 
     # Libbacktrace, for stacktraces in Linux/Android debug builds.
-    Dependency('buildtools/libbacktrace.zip',
-     'https://github.com/ianlancetaylor/libbacktrace/archive/177940370e4a6b2509e92a0aaa9749184e64af43.zip',
-     'b723fe9d671d1ab54df1297f6afbf2893a41c3ea', 'all'),
+    # From https://github.com/ianlancetaylor/libbacktrace/archive/177940370e4a6b2509e92a0aaa9749184e64af43.zip
+    Dependency(
+        'buildtools/libbacktrace.zip',
+        'https://storage.googleapis.com/perfetto/libbacktrace-177940370e4a6b2509e92a0aaa9749184e64af43.zip',
+        '21ac9a4209f7aeef766c482be53a7fa365063c031c7077e2070b491202983b31',
+        'all'),
 
     # Sqlite for the trace processing library.
     # This is the amalgamated source whose compiled output is meant to be faster.
-    # We still pull the full source for the extensions (not amalgamated).
-    Dependency('buildtools/sqlite.zip',
-     'https://storage.googleapis.com/perfetto/sqlite-amalgamation-3320300.zip',
-     '0c805bea134712a903290a26b2a61c3a8a3bd8cc', 'all'),
-    Dependency('buildtools/sqlite_src.zip',
-     'https://storage.googleapis.com/perfetto/sqlite-src-3320300.zip',
-     'd46f60e0fb2b1a959ae59bfa881fc95a510c4d21', 'all'),
+    # We still pull the full source for the extensions (which are not available
+    # in the amalgamation).
+    Dependency(
+        'buildtools/sqlite.zip',
+        'https://storage.googleapis.com/perfetto/sqlite-amalgamation-3320300.zip',
+        'e9cec01d4519e2d49b3810615237325263fe1feaceae390ee12b4a29bd73dbe2',
+        'all'),
+    Dependency(
+        'buildtools/sqlite_src',
+        'https://chromium.googlesource.com/external/github.com/sqlite/sqlite.git',
+        'ee3686eb50c0e3dbb087c9a0976f7e37e1b014ae',  # refs/tags/version-3.32.3.
+        'all'),
 
     # JsonCpp for legacy json import. Used only by the trace processor in
     # standalone builds.
-    Dependency('buildtools/jsoncpp.zip',
-     'https://github.com/open-source-parsers/jsoncpp/archive/1.9.3.zip',
-     'ec1cf26bf4e60822dbb31576e1a83ce1b9fbc36a', 'all'),
+    Dependency(
+        'buildtools/jsoncpp',
+        'https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp.git',
+        '6aba23f4a8628d599a9ef7fa4811c4ff6e4070e2',  # refs/tags/1.9.3.
+        'all'),
 
     # These dependencies are for libunwindstack, which is used by src/profiling.
     Dependency('buildtools/android-core',
-     'https://android.googlesource.com/platform/system/core.git',
-     '8bf4e29e44098e3232ff646331675fb113064162', 'all'),
+               'https://android.googlesource.com/platform/system/core.git',
+               '9e6cef7f07d8c11b3ea820938aeb7ff2e9dbaa52', 'all'),
+    Dependency('buildtools/android-unwinding',
+               'https://android.googlesource.com/platform/system/unwinding.git',
+               'a449157de3531a05c138bfac2894e4831c869f6b', 'all'),
+    Dependency('buildtools/android-logging',
+               'https://android.googlesource.com/platform/system/logging.git',
+               '7b36b566c9113fc703d68f76e8f40c0c2432481c', 'all'),
+    Dependency('buildtools/android-libbase',
+               'https://android.googlesource.com/platform/system/libbase.git',
+               '78f1c2f83e625bdf66d55b48bdb3a301c20d2fb3', 'all'),
+    Dependency('buildtools/android-libprocinfo',
+               'https://android.googlesource.com/platform/system/libprocinfo.git',
+               'bd752195f52109fb0dca525236ab9dec74b986e1', 'all'),
     Dependency('buildtools/lzma',
-     'https://android.googlesource.com/platform/external/lzma.git',
-     '7851dce6f4ca17f5caa1c93a4e0a45686b1d56c3', 'all'),
+               'https://android.googlesource.com/platform/external/lzma.git',
+               '7851dce6f4ca17f5caa1c93a4e0a45686b1d56c3', 'all'),
     Dependency('buildtools/zlib',
-     'https://android.googlesource.com/platform/external/zlib.git',
-     'dfa0646a03b4e1707469e04dc931b09774968fe6', 'all'),
+               'https://android.googlesource.com/platform/external/zlib.git',
+               '5c85a2da4c13eda07f69d81a1579a5afddd35f59', 'all'),
     Dependency('buildtools/bionic',
-     'https://android.googlesource.com/platform/bionic.git',
-     'a60488109cda997dfd83832731c8527feaa2825e', 'all'),
+               'https://android.googlesource.com/platform/bionic.git',
+               'a60488109cda997dfd83832731c8527feaa2825e', 'all'),
 
     # Example traces for regression tests.
     Dependency(
         'buildtools/test_data.zip',
-        'https://storage.googleapis.com/perfetto/test-data-20200716-213947.zip',
-        'ed6a14fd5196479bb9e0c18805fd45b79fcbee8f',
+        'https://storage.googleapis.com/perfetto/test-data-20201221-112454.zip',
+        'bdb45847b3bfc3f12f10be69e669187e114944ca1ea386a455b0f31d3b1b2c1c',
         'all',
     ),
 
     # Linenoise, used only by trace_processor in standalone builds.
     Dependency('buildtools/linenoise',
-     'https://fuchsia.googlesource.com/third_party/linenoise.git',
-     'c894b9e59f02203dbe4e2be657572cf88c4230c3', 'all'),
+               'https://fuchsia.googlesource.com/third_party/linenoise.git',
+               'c894b9e59f02203dbe4e2be657572cf88c4230c3', 'all'),
 ]
 
 # Dependencies required to build Android code.
@@ -164,68 +235,85 @@
 # - https://dl.google.com/android/repository/sys-img/android/sys-img.xml
 BUILD_DEPS_ANDROID = [
     # Android NDK
-    Dependency('buildtools/ndk.zip',
-     'https://dl.google.com/android/repository/android-ndk-r17b-darwin-x86_64.zip',
-     'f990aafaffec0b583d2c5420bfa622e52ac14248', 'darwin'),
-    Dependency('buildtools/ndk.zip',
-     'https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip',
-     'dd5762ee7ef4995ad04fe0c45a608c344d99ca9f', 'linux'),
+    Dependency(
+        'buildtools/ndk.zip',
+        'https://dl.google.com/android/repository/android-ndk-r17b-darwin-x86_64.zip',
+        'd21072c04ffcf8a723a4dba3837c886bd30c18c0623a4d0ddc53850e2222d27f',
+        'darwin'),
+    Dependency(
+        'buildtools/ndk.zip',
+        'https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip',
+        '5dfbbdc2d3ba859fed90d0e978af87c71a91a5be1f6e1c40ba697503d48ccecd',
+        'linux'),
 ]
 
 # Dependencies required to run Android tests.
 TEST_DEPS_ANDROID = [
     # Android emulator images.
-    Dependency('buildtools/aosp-arm.zip',
-     'https://storage.googleapis.com/perfetto/aosp-02022018-arm.zip',
-     'a480d5e7d3ca888b0a58fe15ce76b1791537429a', 'all'),
+    Dependency(
+        'buildtools/aosp-arm.zip',
+        'https://storage.googleapis.com/perfetto/aosp-02022018-arm.zip',
+        'f5c7a3a22ad7aa0bd14ba467e8697e1e917d306699bd25622aa4419a413b9b67',
+        'all'),
 
     # platform-tools.zip contains adb binaries.
-    Dependency('buildtools/android_sdk/platform-tools.zip',
-     'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip',
-     'e75b6137dc444f777eb02f44a6d9819b3aabff82', 'darwin'),
-    Dependency('buildtools/android_sdk/platform-tools.zip',
-     'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip',
-     '00de8a6631405b617c10f68cd11ff2e1cd528e23', 'linux'),
+    Dependency(
+        'buildtools/android_sdk/platform-tools.zip',
+        'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip',
+        '98d392cbd21ca20d643c7e1605760cc49075611e317c534096b5564053f4ac8e',
+        'darwin'),
+    Dependency(
+        'buildtools/android_sdk/platform-tools.zip',
+        'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip',
+        '90208207521d85abf0d46e3374aa4e04b7aff74e4f355c792ac334de7a77e50b',
+        'linux'),
 
     # Android emulator binaries.
-    Dependency('buildtools/emulator',
-     'https://android.googlesource.com/platform/prebuilts/android-emulator.git',
-     '4b260028dc27bc92c39bee9129cb2ba839970956', 'all'),
+    Dependency(
+        'buildtools/emulator',
+        'https://android.googlesource.com/platform/prebuilts/android-emulator.git',
+        '4b260028dc27bc92c39bee9129cb2ba839970956', 'all'),
 ]
 
 # This variable is updated by tools/roll-catapult-trace-viewer.
-CATAPULT_SHA1 = '5f77256e1b24851a05e8580438b532e2480dd7fd'
+CATAPULT_SHA256 = 'b30108e05268ce6c65bb4126b65f6bfac165d17f5c1fd285046e7e6fd76c209f'
 
-TYPEFACES_SHA1 = '4fb455de506f8a2859dc5264b8448c2559b08ab8'
+TYPEFACES_SHA256 = 'b3f0f14eeecd4555ae94f897ec246b2c6e046ce0ea417407553f5767e7812575'
 
 UI_DEPS = [
-    Dependency('buildtools/nodejs.tgz',
-     'https://storage.googleapis.com/perfetto/node-v10.3.0-darwin-x64.tar.gz',
-     '6d9a122785f38c256add3b25f74adf125497861a', 'darwin'),
-    Dependency('buildtools/nodejs.tgz',
-     'https://storage.googleapis.com/perfetto/node-v10.3.0-linux-x64.tar.xz',
-     '118f6ea19f75089b3f12ac2ddfce357bff872b5e', 'linux'),
-    Dependency('buildtools/emsdk/emscripten.tgz',
-     'https://storage.googleapis.com/perfetto/emscripten-1.37.40.tar.gz',
-     '588c28221321ebbdfc8e3a6f47ea6106f589669b', 'all'),
-    Dependency('buildtools/emsdk/llvm.tgz',
-     'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-darwin.tar.gz',
-     '7a894ef0a52821c62f6abaac552dc4ce5d424607', 'darwin'),
-    Dependency('buildtools/emsdk/llvm.tgz',
-     'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-static-linux.tar.gz',
-     '478501b9b7a14884e546c84efe209a90052cbb07', 'linux'),
-    Dependency('buildtools/d8.tgz',
-     'https://storage.googleapis.com/perfetto/d8-linux-5.7.492.65.tar.gz',
-     '95e82ad7faf0a6f74d950c2aa65e3858b7bdb6c6', 'linux'),
-    Dependency('buildtools/d8.tgz',
-     'https://storage.googleapis.com/perfetto/d8-darwin-6.6.346.32.tar.gz',
-     '1abd630619bb1977ab62095570a113d782a1545d', 'darwin'),
-    Dependency('buildtools/catapult_trace_viewer.tgz',
-     'https://storage.googleapis.com/perfetto/catapult_trace_viewer-%s.tar.gz' %
-     CATAPULT_SHA1, CATAPULT_SHA1, 'all'),
-    Dependency('buildtools/typefaces.tgz',
-     'https://storage.googleapis.com/perfetto/typefaces-%s.tar.gz' %
-     TYPEFACES_SHA1, TYPEFACES_SHA1, 'all')
+    Dependency(
+        'buildtools/nodejs.tgz',
+        'https://storage.googleapis.com/perfetto/node-v12.18.3-darwin-x64.tar.gz',
+        'af376caf114bdd5d7e566dbf7590e9077ffc01f9b2692eb2651f31d7219a30bb',
+        'darwin'),
+    Dependency(
+        'buildtools/nodejs.tgz',
+        'https://storage.googleapis.com/perfetto/node-v12.18.3-linux-x64.tar.gz',
+        '0be428afce5b24f799f3b1ab8902d9d91094b929ac58d2b1cec29436ea2d742c',
+        'linux'),
+    Dependency(
+        'buildtools/emsdk/emscripten.tgz',
+        'https://storage.googleapis.com/perfetto/emscripten-1.37.40.tar.gz',
+        '40d8a095c510e5e5e2032131e0dc5d38f996172deb0415588b702146d1ea8bd5',
+        'all'),
+    Dependency(
+        'buildtools/emsdk/llvm.tgz',
+        'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-darwin.tar.gz',
+        'ed51f3a467c0a5af5365abb448794734affcc2932fa19d62be233dee435c89ea',
+        'darwin'),
+    Dependency(
+        'buildtools/emsdk/llvm.tgz',
+        'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-static-linux.tar.gz',
+        '257da419f92d305025777f340b79ee9f3d3077af38f5e7efb0fac1060d3ea6d9',
+        'linux'),
+    Dependency(
+        'buildtools/catapult_trace_viewer.tgz',
+        'https://storage.googleapis.com/perfetto/catapult_trace_viewer-%s.tar.gz'
+        % CATAPULT_SHA256, CATAPULT_SHA256, 'all'),
+    Dependency(
+        'buildtools/typefaces.tgz',
+        'https://storage.googleapis.com/perfetto/typefaces-%s.tar.gz' %
+        TYPEFACES_SHA256, TYPEFACES_SHA256, 'all')
 ]
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -259,7 +347,7 @@
   if not os.path.exists(path):
     return None
   with open(path, 'rb') as f:
-    return hashlib.sha1(f.read()).hexdigest()
+    return hashlib.sha256(f.read()).hexdigest()
 
 
 def ExtractZipfilePreservePermissions(zf, info, path):
@@ -278,8 +366,17 @@
     return False
   if check_only:
     return True
+  path = path.replace('/', os.sep)
+
+  # Git creates read-only files on windows, which cause failures with rmtree.
+  # This seems the socially accepted way to deal with it.
+  # See https://bugs.python.org/issue19643 .
+  def del_read_only_for_windows(_action, name, _exc):
+    os.chmod(name, stat.S_IWRITE)
+    os.remove(name)
+
   if os.path.exists(path):
-    shutil.rmtree(path)
+    shutil.rmtree(path, onerror=del_read_only_for_windows)
   MkdirRecursive(path)
   logging.info('Fetching %s @ %s into %s', git_url, revision, path)
   subprocess.check_call(['git', 'init', path], cwd=path)
@@ -290,7 +387,12 @@
   return True
 
 
-def InstallNodeModules():
+def InstallNodeModules(force_clean=False):
+  if force_clean:
+    node_modules = os.path.join(UI_DIR, 'node_modules')
+    logging.info('Clearing %s', node_modules)
+    subprocess.check_call(['git', 'clean', '-qxffd', node_modules],
+                          cwd=ROOT_DIR)
   logging.info("Running npm install in {0}".format(UI_DIR))
   subprocess.check_call([os.path.join(UI_DIR, 'npm'), 'install', '--no-save'],
                         cwd=UI_DIR)
@@ -318,15 +420,15 @@
       if dep.source_url.endswith('.git'):
         continue
       logging.info('Downloading %s from %s', dep.target_platform,
-                                             dep.source_url)
+                   dep.source_url)
       with tempfile.NamedTemporaryFile(delete=False) as f:
         f.close()
-        DownloadURL(url, f.name)
-        actual_sha1 = HashLocalFile(f.name)
+        DownloadURL(dep.source_url, f.name)
+        actual_checksum = HashLocalFile(f.name)
         os.unlink(f.name)
-        if (actual_sha1 != dep.sha1):
-          logging.fatal('SHA1 mismatch for {} expected {} was {}'.format(
-              dep.source_url, dep.sha1, actual_sha1))
+        if (actual_checksum != dep.checksum):
+          logging.fatal('SHA-256 mismatch for {} expected {} was {}'.format(
+              dep.source_url, dep.checksum, actual_checksum))
 
 
 def Main():
@@ -345,6 +447,7 @@
   if args.ui:
     deps += UI_DEPS
   deps_updated = False
+  nodejs_updated = False
 
   for dep in deps:
     if (dep.target_platform != 'all' and
@@ -352,33 +455,36 @@
       continue
     local_path = os.path.join(ROOT_DIR, dep.target_folder)
     if dep.source_url.endswith('.git'):
-      deps_updated |= CheckoutGitRepo(local_path, dep.source_url, dep.sha1,
+      deps_updated |= CheckoutGitRepo(local_path, dep.source_url, dep.checksum,
                                       args.check_only)
       continue
     is_zip = local_path.endswith('.zip') or local_path.endswith('.tgz')
     zip_target_dir = local_path[:-4] if is_zip else None
     zip_dir_stamp = os.path.join(zip_target_dir, '.stamp') if is_zip else None
 
-    if ((not is_zip and HashLocalFile(local_path) == dep.sha1) or
-        (is_zip and ReadFile(zip_dir_stamp) == dep.sha1)):
+    if ((not is_zip and HashLocalFile(local_path) == dep.checksum) or
+        (is_zip and ReadFile(zip_dir_stamp) == dep.checksum)):
       continue
     deps_updated = True
     if args.check_only:
       continue
     MkdirRecursive(os.path.dirname(dep.target_folder))
-    if HashLocalFile(local_path) != dep.sha1:
+    if HashLocalFile(local_path) != dep.checksum:
       download_path = local_path + '.tmp'
       logging.info('Downloading %s from %s', local_path, dep.source_url)
       DownloadURL(dep.source_url, download_path)
       os.chmod(download_path, 0o755)
-      actual_sha1 = HashLocalFile(download_path)
-      if (actual_sha1 != dep.sha1):
+      actual_checksum = HashLocalFile(download_path)
+      if (actual_checksum != dep.checksum):
         os.remove(download_path)
-        logging.fatal('SHA1 mismatch for {} expected {} was {}'.format(
-            download_path, dep.sha1, actual_sha1))
+        logging.fatal('SHA-256 mismatch for {} expected {} was {}'.format(
+            download_path, dep.checksum, actual_checksum))
         return 1
-      os.rename(download_path, local_path)
-    assert (HashLocalFile(local_path) == dep.sha1)
+      shutil.move(download_path, local_path)
+      if 'nodejs' in dep.target_folder:
+        nodejs_updated = True
+
+    assert (HashLocalFile(local_path) == dep.checksum)
 
     if is_zip:
       logging.info('Extracting %s into %s' % (local_path, zip_target_dir))
@@ -408,7 +514,7 @@
 
       # Create stamp and remove the archive.
       with open(zip_dir_stamp, 'w') as stamp_file:
-        stamp_file.write(dep.sha1)
+        stamp_file.write(dep.checksum)
       os.remove(local_path)
 
   if args.ui:
@@ -416,7 +522,7 @@
     if args.check_only:
       deps_updated = not CheckNodeModules()
     else:
-      InstallNodeModules()
+      InstallNodeModules(force_clean=nodejs_updated)
 
   if args.check_only:
     if not deps_updated:
diff --git a/tools/java_heap_dump b/tools/java_heap_dump
index f84a594..ced597e 100755
--- a/tools/java_heap_dump
+++ b/tools/java_heap_dump
@@ -50,7 +50,8 @@
   }}
 }}
 
-duration_ms: 20000
+data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
+duration_ms: {duration_ms}
 '''
 
 CONTINUOUS_DUMP = """
@@ -64,6 +65,7 @@
                 'perfetto --txt -c - -o '
                 '/data/misc/perfetto-traces/java-profile-{user} -d')
 
+
 def main(argv):
   parser = argparse.ArgumentParser()
   parser.add_argument(
@@ -102,6 +104,11 @@
       "--print-config",
       action="store_true",
       help="Print config instead of running. For debugging.")
+  parser.add_argument(
+      "--stop-when-done",
+      action="store_true",
+      help="On recent builds of S, use a new method to stop the profile when "
+           "the dump is done. Previously, we would hardcode a duration.")
 
   args = parser.parse_args()
 
@@ -139,9 +146,21 @@
   if args.continuous_dump:
     continuous_dump_cfg = CONTINUOUS_DUMP.format(
         dump_interval=args.continuous_dump)
+
+  # TODO(fmayer): Once the changes have been in S for long enough, make this
+  #               the default for S+.
+  if args.stop_when_done:
+    duration_ms = 1000
+    data_source_stop_timeout_ms = 100000
+  else:
+    duration_ms = 20000
+    data_source_stop_timeout_ms = 0
+
   cfg = CFG.format(
       target_cfg=target_cfg,
-      continuous_dump_config=continuous_dump_cfg)
+      continuous_dump_config=continuous_dump_cfg,
+      duration_ms=duration_ms,
+      data_source_stop_timeout_ms=data_source_stop_timeout_ms)
   if not args.no_versions:
     cfg += PACKAGES_LIST_CFG
 
diff --git a/tools/multithreaded_alloc.cc b/tools/multithreaded_alloc.cc
new file mode 100644
index 0000000..174fd25
--- /dev/null
+++ b/tools/multithreaded_alloc.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <iterator>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/time.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/profiling/memory/heap_profile.h"
+
+namespace {
+
+void EnabledCallback(void*, const AHeapProfileEnableCallbackInfo*);
+
+std::atomic<bool> done;
+std::atomic<uint64_t> allocs{0};
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wglobal-constructors"
+#pragma GCC diagnostic ignored "-Wexit-time-destructors"
+std::mutex g_wake_up_mutex;
+std::condition_variable g_wake_up_cv;
+uint64_t g_rate = 0;
+
+uint32_t g_heap_id = AHeapProfile_registerHeap(
+    AHeapInfo_setEnabledCallback(AHeapInfo_create("test_heap"),
+                                 EnabledCallback,
+                                 nullptr));
+
+#pragma GCC diagnostic pop
+
+void EnabledCallback(void*, const AHeapProfileEnableCallbackInfo* info) {
+  std::lock_guard<std::mutex> l(g_wake_up_mutex);
+  g_rate = AHeapProfileEnableCallbackInfo_getSamplingInterval(info);
+  g_wake_up_cv.notify_all();
+}
+
+uint64_t ScrambleAllocId(uint64_t alloc_id, uint32_t thread_idx) {
+  return thread_idx | (~alloc_id << 24);
+}
+
+void Thread(uint32_t thread_idx, uint64_t pending_allocs) {
+  PERFETTO_CHECK(thread_idx < 1 << 24);
+  uint64_t alloc_id = 0;
+  size_t thread_allocs = 0;
+  while (!done.load(std::memory_order_relaxed)) {
+    AHeapProfile_reportAllocation(g_heap_id,
+                                  ScrambleAllocId(alloc_id, thread_idx), 1);
+    if (alloc_id > pending_allocs)
+      AHeapProfile_reportFree(
+          g_heap_id, ScrambleAllocId(alloc_id - pending_allocs, thread_idx));
+    alloc_id++;
+    thread_allocs++;
+  }
+  allocs.fetch_add(thread_allocs, std::memory_order_relaxed);
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  if (argc != 4) {
+    PERFETTO_FATAL("%s NUMBER_THREADS RUNTIME_MS PENDING_ALLOCS", argv[0]);
+  }
+
+  perfetto::base::Optional<uint64_t> opt_no_threads =
+      perfetto::base::CStringToUInt64(argv[1]);
+  if (!opt_no_threads) {
+    PERFETTO_FATAL("Invalid number of threads: %s", argv[1]);
+  }
+  uint64_t no_threads = *opt_no_threads;
+
+  perfetto::base::Optional<uint64_t> opt_runtime_ms =
+      perfetto::base::CStringToUInt64(argv[2]);
+  if (!opt_runtime_ms) {
+    PERFETTO_FATAL("Invalid runtime: %s", argv[2]);
+  }
+  uint64_t runtime_ms = *opt_runtime_ms;
+
+  perfetto::base::Optional<uint64_t> opt_pending_allocs =
+      perfetto::base::CStringToUInt64(argv[3]);
+  if (!opt_runtime_ms) {
+    PERFETTO_FATAL("Invalid number of pending allocs: %s", argv[3]);
+  }
+  uint64_t pending_allocs = *opt_pending_allocs;
+
+  std::unique_lock<std::mutex> l(g_wake_up_mutex);
+  g_wake_up_cv.wait(l, [] { return g_rate > 0; });
+
+  perfetto::base::TimeMillis end =
+      perfetto::base::GetWallTimeMs() + perfetto::base::TimeMillis(runtime_ms);
+  std::vector<std::thread> threads;
+  for (size_t i = 0; i < static_cast<size_t>(no_threads); ++i)
+    threads.emplace_back(Thread, i, pending_allocs);
+
+  perfetto::base::TimeMillis current = perfetto::base::GetWallTimeMs();
+  while (current < end) {
+    usleep(useconds_t((end - current).count()) * 1000);
+    current = perfetto::base::GetWallTimeMs();
+  }
+
+  done.store(true, std::memory_order_relaxed);
+
+  for (std::thread& th : threads)
+    th.join();
+
+  printf("%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 "\n",
+         no_threads, runtime_ms, pending_allocs, g_rate,
+         allocs.load(std::memory_order_relaxed));
+  return 0;
+}
diff --git a/tools/roll-catapult-trace-viewer b/tools/roll-catapult-trace-viewer
index c163885..84e0eb5 100755
--- a/tools/roll-catapult-trace-viewer
+++ b/tools/roll-catapult-trace-viewer
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 # Builds the current version of catapult, uploads it to GCS and updates the
-# pinned SHA1 in install-build-deps.
+# pinned SHA256 in install-build-deps.
 
 set -e
 
@@ -69,15 +69,15 @@
   tar -zcf "$ARCHIVE" catapult_trace_viewer.{js,html}
 )
 
-SHA1CMD='import hashlib; import sys; sha1=hashlib.sha1(); sha1.update(sys.stdin.read()); print(sha1.hexdigest())'
-SHA1=$(python -c "$SHA1CMD" < "$ARCHIVE")
-GCS_TARGET="gs://perfetto/catapult_trace_viewer-$SHA1.tar.gz"
+SHA256CMD='import hashlib; import sys; sha1=hashlib.sha256(); sha1.update(sys.stdin.read()); print(sha1.hexdigest())'
+SHA256=$(python -c "$SHA256CMD" < "$ARCHIVE")
+GCS_TARGET="gs://perfetto/catapult_trace_viewer-$SHA256.tar.gz"
 gsutil cp -n -a public-read "$ARCHIVE" "$GCS_TARGET"
 rm -rf "$OUTDIR"
 
 # Update the reference to the new prebuilt in tools/install-build-deps.
 sed -i -e \
-    "s/^CATAPULT_SHA1 =.*/CATAPULT_SHA1 = '"$SHA1"'/g" \
+    "s/^CATAPULT_SHA256 =.*/CATAPULT_SHA256 = '"$SHA256"'/g" \
      "$PROJECT_ROOT/tools/install-build-deps"
 
-"$PROJECT_ROOT/tools/install-build-deps" --ui
\ No newline at end of file
+"$PROJECT_ROOT/tools/install-build-deps" --ui
diff --git a/tools/run_android_test b/tools/run_android_test
index 4707796..aa3d70d 100755
--- a/tools/run_android_test
+++ b/tools/run_android_test
@@ -149,6 +149,8 @@
   AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,)))
   AdbCall('shell', 'rm -rf "%s/*";  ' % trace_dir)
   AdbCall('shell', 'mkdir -p /data/nativetest')
+  AdbCall('shell', 'echo 0 > /d/tracing/tracing_on')
+
   # This needs to go into /data/nativetest in order to have the system linker
   # namespace applied, which we need in order to link libdexfile_external.so.
   # This gets linked into our tests via libundwindstack.so.
diff --git a/tools/run_ftrace_proto_gen b/tools/run_ftrace_proto_gen
index c8e9ca7..d1770f5 100755
--- a/tools/run_ftrace_proto_gen
+++ b/tools/run_ftrace_proto_gen
@@ -2,7 +2,7 @@
 
 # This script generates .proto files for ftrace events from the /format files
 # in src/traced/probes/ftrace/test/data/*/events/.
-# Only the events in the whitelist are translated.
+# Only the events in the event_list are translated.
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 if [ "$BUILDDIR" == "" ]; then
@@ -23,7 +23,7 @@
 cd "$DIR/.."
 
 "$BUILDDIR/ftrace_proto_gen" \
-  --whitelist_path "$DIR/ftrace_proto_gen/event_whitelist" \
+  --event_list "$DIR/ftrace_proto_gen/event_list" \
   --output_dir "$DIR/../protos/perfetto/trace/ftrace/" \
   --proto_descriptor "$BUILDDIR/$DESCRIPTOR" \
   --update_build_files \
diff --git a/tools/run_python_api_tests.py b/tools/run_python_api_tests.py
new file mode 100755
index 0000000..789a1f0
--- /dev/null
+++ b/tools/run_python_api_tests.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# 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.
+
+import argparse
+import os
+import sys
+import unittest
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+def main():
+  # Append test and src paths so that all imports are loaded in correctly
+  sys.path.append(os.path.join(ROOT_DIR, 'test', 'trace_processor', 'python'))
+  sys.path.append(
+      os.path.join(ROOT_DIR, 'src', 'trace_processor', 'python', 'perfetto'))
+  import api_unittest
+  import api_integrationtest
+
+  # Set paths to trace_processor_shell and root directory as environment
+  # variables
+  parser = argparse.ArgumentParser()
+  parser.add_argument("shell", type=str)
+  os.environ["SHELL_PATH"] = parser.parse_args().shell
+  os.environ["ROOT_DIR"] = ROOT_DIR
+
+  # Initialise test suite
+  loader = unittest.TestLoader()
+  suite = unittest.TestSuite()
+
+  # Add all relevant tests to test suite
+  suite.addTests(loader.loadTestsFromModule(api_unittest))
+  suite.addTests(loader.loadTestsFromModule(api_integrationtest))
+
+  # Initialise runner to run all tests in suite
+  runner = unittest.TextTestRunner(verbosity=3)
+  result = runner.run(suite)
+
+  return 0 if result.wasSuccessful() else 1
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/touch_file.py b/tools/touch_file.py
new file mode 100644
index 0000000..899a0ee
--- /dev/null
+++ b/tools/touch_file.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# 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.
+
+# This script writes an empty file to disk at the specified path. The main use
+# of this is to allow noop targets to be written in GN which simply propogate
+# information to the GN description files without actually generating any data.
+
+import argparse
+import sys
+import os
+
+
+def touch(fname, times=None):
+  with open(fname, 'a'):
+    os.utime(fname, times)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--output')
+  args = parser.parse_args()
+
+  touch(args.output)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/trace_processor b/tools/trace_processor
index 710f0b8..0fa0608 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -31,8 +31,8 @@
 import subprocess
 
 TRACE_PROCESSOR_SHELL_SHAS = {
-    'linux': '7e49a1c8b957287777f792ab5d6533b6f5c67c63',
-    'mac': 'b90e85cdcdc8c7eefb3dc75fd9c4e6c964635394',
+    'linux': '1e0a01e01026f52a32c5819904e6d193d29da600',
+    'mac': 'b19951d3e25fa6e091ef3bcd59a77b39217791a2',
 }
 TRACE_PROCESSOR_SHELL_PATH = tempfile.gettempdir()
 TRACE_PROCESSOR_SHELL_BASE_URL = ('https://storage.googleapis.com/perfetto/')
diff --git a/tools/trace_to_text/BUILD.gn b/tools/trace_to_text/BUILD.gn
index bb0cf7b..a89db03 100644
--- a/tools/trace_to_text/BUILD.gn
+++ b/tools/trace_to_text/BUILD.gn
@@ -107,6 +107,8 @@
     "main.cc",
     "symbolize_profile.cc",
     "symbolize_profile.h",
+    "trace_to_hprof.cc",
+    "trace_to_hprof.h",
     "trace_to_json.cc",
     "trace_to_json.h",
     "trace_to_profile.cc",
@@ -115,9 +117,6 @@
     "trace_to_systrace.h",
     "trace_to_text.h",
   ]
-  if (enable_perfetto_version_gen) {
-    deps += [ "//gn/standalone:gen_git_revision" ]
-  }
 }
 
 # Lite target for the WASM UI. Doesn't have any dependency on libprotobuf-full.
@@ -150,10 +149,12 @@
   ]
 }
 
-wasm_lib("trace_to_text_wasm") {
-  name = "trace_to_text"
-  deps = [
-    ":lite",
-    "../../gn:default_deps",
-  ]
+if (enable_perfetto_ui) {
+  wasm_lib("trace_to_text_wasm") {
+    name = "trace_to_text"
+    deps = [
+      ":lite",
+      "../../gn:default_deps",
+    ]
+  }
 }
diff --git a/tools/trace_to_text/deobfuscate_profile.cc b/tools/trace_to_text/deobfuscate_profile.cc
index b6e8b8c..dfdb67b 100644
--- a/tools/trace_to_text/deobfuscate_profile.cc
+++ b/tools/trace_to_text/deobfuscate_profile.cc
@@ -17,7 +17,10 @@
 #include <stdio.h>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/utils.h"
 #include "perfetto/profiling/deobfuscator.h"
 #include "perfetto/trace_processor/trace_processor.h"
 #include "tools/trace_to_text/deobfuscate_profile.h"
@@ -25,62 +28,22 @@
 
 namespace perfetto {
 namespace trace_to_text {
-namespace {
-
-bool ParseFile(profiling::ProguardParser* p, FILE* f) {
-  std::vector<std::string> lines;
-  size_t n = 0;
-  char* line = nullptr;
-  ssize_t rd = 0;
-  bool success = true;
-  do {
-    rd = getline(&line, &n, f);
-    // Do not read empty line that terminates the output.
-    if (rd > 1) {
-      // Remove newline character.
-      PERFETTO_DCHECK(line[rd - 1] == '\n');
-      line[rd - 1] = '\0';
-      success = p->AddLine(line);
-    }
-  } while (rd > 1 && success);
-  free(line);
-  return success;
-}
-}  // namespace
 
 int DeobfuscateProfile(std::istream* input, std::ostream* output) {
   base::ignore_result(input);
   base::ignore_result(output);
-  auto maybe_map = GetPerfettoProguardMapPath();
-  if (!maybe_map) {
+  auto maybe_map = profiling::GetPerfettoProguardMapPath();
+  if (maybe_map.empty()) {
     PERFETTO_ELOG("No PERFETTO_PROGUARD_MAP specified.");
     return 1;
   }
-  base::ScopedFstream f(fopen(maybe_map->c_str(), "r"));
-  if (!f) {
-    PERFETTO_ELOG("Failed to open %s", maybe_map->c_str());
+  if (!profiling::ReadProguardMapsToDeobfuscationPackets(
+          maybe_map, [output](const std::string& trace_proto) {
+            *output << trace_proto;
+          })) {
     return 1;
   }
-  profiling::ProguardParser parser;
-  if (!ParseFile(&parser, *f)) {
-    PERFETTO_ELOG("Failed to parse %s", maybe_map->c_str());
-    return 1;
-  }
-  std::map<std::string, profiling::ObfuscatedClass> obfuscation_map =
-      parser.ConsumeMapping();
 
-  trace_processor::Config config;
-  std::unique_ptr<trace_processor::TraceProcessor> tp =
-      trace_processor::TraceProcessor::CreateInstance(config);
-
-  if (!ReadTrace(tp.get(), input))
-    PERFETTO_FATAL("Failed to read trace.");
-
-  tp->NotifyEndOfFile();
-  DeobfuscateDatabase(tp.get(), obfuscation_map,
-                      [output](const std::string& packet_proto) {
-                        WriteTracePacket(packet_proto, output);
-                      });
   return 0;
 }
 
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index e3c084c..d5f05c9 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -23,18 +23,15 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/version.h"
 #include "tools/trace_to_text/deobfuscate_profile.h"
 #include "tools/trace_to_text/symbolize_profile.h"
+#include "tools/trace_to_text/trace_to_hprof.h"
 #include "tools/trace_to_text/trace_to_json.h"
 #include "tools/trace_to_text/trace_to_profile.h"
 #include "tools/trace_to_text/trace_to_systrace.h"
 #include "tools/trace_to_text/trace_to_text.h"
 
-#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
-#include "perfetto_version.gen.h"
-#else
-#define PERFETTO_GET_GIT_REVISION() "unknown"
-#endif
 
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <unistd.h>
@@ -53,6 +50,7 @@
           "[trace.pb] "
           "[trace.txt]\n"
           "\nProfile mode only:\n"
+          "\t--perf generate a perf profile\n"
           "\t--timestamps TIMESTAMP1,TIMESTAMP2,... generate profiles "
           "only for these timestamps\n"
           "\t--pid PID generate profiles only for this process id\n",
@@ -76,9 +74,10 @@
   uint64_t pid = 0;
   std::vector<uint64_t> timestamps;
   bool full_sort = false;
+  bool perf_profile = false;
   for (int i = 1; i < argc; i++) {
     if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
-      printf("%s\n", PERFETTO_GET_GIT_REVISION());
+      printf("%s\n", base::GetVersionString());
       return 0;
     } else if (strcmp(argv[i], "-t") == 0 ||
                strcmp(argv[i], "--truncate") == 0) {
@@ -102,6 +101,8 @@
       for (const std::string& ts : ts_strings) {
         timestamps.emplace_back(StringToUint64OrDie(ts.c_str()));
       }
+    } else if (strcmp(argv[i], "--perf") == 0) {
+      perf_profile = true;
     } else if (strcmp(argv[i], "--full-sort") == 0) {
       full_sort = true;
     } else {
@@ -121,12 +122,14 @@
       PERFETTO_FATAL("Could not open %s", file_path);
     input_stream = &file_istream;
   } else {
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
     if (isatty(STDIN_FILENO)) {
       PERFETTO_ELOG("Reading from stdin but it's connected to a TTY");
       PERFETTO_LOG("It is unlikely that you want to type in some binary.");
       PERFETTO_LOG("Either pass a file path to the cmdline or pipe stdin");
       return Usage(argv[0]);
     }
+#endif
     input_stream = &std::cin;
   }
 
@@ -144,9 +147,15 @@
 
   std::string format(positional_args[0]);
 
-  if (format != "profile" && (pid != 0 || !timestamps.empty())) {
+  if ((format != "profile" && format != "hprof") &&
+      (pid != 0 || !timestamps.empty())) {
     PERFETTO_ELOG(
-        "--pid and --timestamps are supported only for profile format.");
+        "--pid and --timestamps are supported only for profile "
+        "formats.");
+    return 1;
+  }
+  if (perf_profile && format != "profile") {
+    PERFETTO_ELOG("--perf requires profile format.");
     return 1;
   }
 
@@ -177,8 +186,15 @@
   if (format == "text")
     return TraceToText(input_stream, output_stream);
 
-  if (format == "profile")
-    return TraceToProfile(input_stream, output_stream, pid, timestamps);
+  if (format == "profile") {
+    return perf_profile ? TraceToPerfProfile(input_stream, output_stream, pid,
+                                             timestamps)
+                        : TraceToHeapProfile(input_stream, output_stream, pid,
+                                             timestamps);
+  }
+
+  if (format == "hprof")
+    return TraceToHprof(input_stream, output_stream, pid, timestamps);
 
   if (format == "symbolize")
     return SymbolizeProfile(input_stream, output_stream);
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index 3f20323..f206f16 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -16,7 +16,12 @@
 
 #include "perfetto/profiling/pprof_builder.h"
 
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
 #include <cxxabi.h>
+#endif
+
 #include <inttypes.h>
 
 #include <algorithm>
@@ -47,100 +52,37 @@
 
 namespace {
 
-using ::protozero::proto_utils::kMessageLengthFieldSize;
-using ::protozero::proto_utils::MakeTagLengthDelimited;
-using ::protozero::proto_utils::WriteVarInt;
-
-struct View {
-  const char* type;
-  const char* unit;
-  const char* aggregator;
-  const char* filter;
-};
+using ::perfetto::trace_processor::Iterator;
 
 void MaybeDemangle(std::string* name) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  char* data = nullptr;
+#else
   int ignored;
   char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
+#endif
   if (data) {
     *name = data;
     free(data);
   }
 }
 
-const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
-const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size > 0"};
-const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
-                             "size > 0"};
-const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
+uint64_t ToPprofId(int64_t id) {
+  PERFETTO_DCHECK(id >= 0);
+  return static_cast<uint64_t>(id) + 1;
+}
 
-const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
-                       kSpaceView};
-
-using trace_processor::Iterator;
-
-constexpr const char* kQueryProfiles =
-    "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
-    "from heap_profile_allocation hpa, "
-    "process p where p.upid = hpa.upid;";
-
-int64_t GetStatsInt(trace_processor::TraceProcessor* tp,
-                    const std::string& name,
-                    uint64_t pid) {
-  auto it = tp->ExecuteQuery("SELECT value from stats where name = '" + name +
-                             "' AND idx = " + std::to_string(pid));
-  if (!it.Next()) {
-    if (!it.Status().ok()) {
-      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
-                              it.Status().message().c_str());
-      return -1;
+std::string AsCsvString(std::vector<uint64_t> vals) {
+  std::string ret;
+  for (size_t i = 0; i < vals.size(); i++) {
+    if (i != 0) {
+      ret += ",";
     }
-    // TODO(fmayer): Remove this case once we always get an entry in the stats
-    // table.
-    return 0;
+    ret += std::to_string(vals[i]);
   }
-  return it.Get(0).AsLong();
+  return ret;
 }
 
-bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
-  bool success = true;
-  int64_t stat = GetStatsInt(tp, "heapprofd_buffer_corrupted", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " ended early due to a buffer corruption."
-                  " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
-                  " CLIENT MEMORY CORRUPTION.",
-                  pid);
-  }
-  stat = GetStatsInt(tp, "heapprofd_buffer_overran", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " ended early due to a buffer overrun.",
-                  pid);
-  }
-
-  stat = GetStatsInt(tp, "heapprofd_rejected_concurrent", pid);
-  if (stat == -1) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
-  } else if (stat > 0) {
-    success = false;
-    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
-                  " was rejected due to a concurrent profile.",
-                  pid);
-  }
-  return success;
-}
-
-struct Callsite {
-  int64_t id;
-  int64_t frame_id;
-};
-
 // Return map from callsite_id to list of frame_ids that make up the callstack.
 std::vector<std::vector<int64_t>> GetCallsiteToFrames(
     trace_processor::TraceProcessor* tp) {
@@ -179,6 +121,17 @@
   return result;
 }
 
+base::Optional<int64_t> GetMaxSymbolId(trace_processor::TraceProcessor* tp) {
+  auto max_symbol_id_it =
+      tp->ExecuteQuery("select max(id) from stack_profile_symbol");
+  if (!max_symbol_id_it.Next()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
+                            max_symbol_id_it.Status().message().c_str());
+    return base::nullopt;
+  }
+  return base::make_optional(max_symbol_id_it.Get(0).AsLong());
+}
+
 struct Line {
   int64_t symbol_id;
   uint32_t line_number;
@@ -205,6 +158,28 @@
   return result;
 }
 
+base::Optional<int64_t> GetStatsEntry(
+    trace_processor::TraceProcessor* tp,
+    const std::string& name,
+    base::Optional<uint64_t> idx = base::nullopt) {
+  std::string query = "select value from stats where name == '" + name + "'";
+  if (idx.has_value())
+    query += " and idx == " + std::to_string(idx.value());
+
+  auto it = tp->ExecuteQuery(query);
+  if (!it.Next()) {
+    if (!it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                              it.Status().message().c_str());
+      return base::nullopt;
+    }
+    // some stats are not present unless non-zero
+    return base::make_optional(0);
+  }
+  return base::make_optional(it.Get(0).AsLong());
+}
+
+// Helper for constructing |perftools.profiles.Profile| protos.
 class GProfileBuilder {
  public:
   GProfileBuilder(
@@ -220,77 +195,57 @@
     PERFETTO_CHECK(empty_id == 0);
   }
 
-  std::vector<Iterator> BuildViewIterators(trace_processor::TraceProcessor* tp,
-                                           uint64_t upid,
-                                           uint64_t ts,
-                                           const char* heap_name) {
-    std::vector<Iterator> view_its;
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      const View& v = kViews[i];
-      std::string query = "SELECT hpa.callsite_id ";
-      query += ", " + std::string(v.aggregator) +
-               " FROM heap_profile_allocation hpa ";
-      // TODO(fmayer): Figure out where negative callsite_id comes from.
-      query += "WHERE hpa.callsite_id >= 0 ";
-      query += "AND hpa.upid = " + std::to_string(upid) + " ";
-      query += "AND hpa.ts <= " + std::to_string(ts) + " ";
-      query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
-      if (v.filter)
-        query += "AND " + std::string(v.filter) + " ";
-      query += "GROUP BY hpa.callsite_id;";
-      view_its.emplace_back(tp->ExecuteQuery(query));
+  void WriteSampleTypes(
+      const std::vector<std::pair<std::string, std::string>>& sample_types) {
+    // The interner might eagerly append to the profile proto, prevent it from
+    // breaking up other messages by making a separate pass.
+    for (const auto& st : sample_types) {
+      Intern(st.first);
+      Intern(st.second);
     }
-    return view_its;
+    for (const auto& st : sample_types) {
+      auto* sample_type = result_->add_sample_type();
+      sample_type->set_type(Intern(st.first));
+      sample_type->set_unit(Intern(st.second));
+    }
   }
 
-  bool WriteAllocations(std::vector<Iterator>* view_its,
-                        std::set<int64_t>* seen_frames) {
-    for (;;) {
-      bool all_next = true;
-      bool any_next = false;
-      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-        Iterator& it = (*view_its)[i];
-        bool next = it.Next();
-        if (!it.Status().ok()) {
-          PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
-                                  it.Status().message().c_str());
-          return false;
-        }
-        all_next = all_next && next;
-        any_next = any_next || next;
-      }
-
-      if (!all_next) {
-        PERFETTO_DCHECK(!any_next);
-        break;
-      }
-
-      auto* gsample = result_->add_sample();
-      protozero::PackedVarInt sample_values;
-      for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-        int64_t callstack_id = (*view_its)[i].Get(0).AsLong();
-        if (i == 0) {
-          auto frames = FramesForCallstack(callstack_id);
-          if (frames.empty())
-            return false;
-          protozero::PackedVarInt location_ids;
-          for (int64_t frame : frames)
-            location_ids.Append(ToPprofId(frame));
-          gsample->set_location_id(location_ids);
-          seen_frames->insert(frames.cbegin(), frames.cend());
-        } else {
-          if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
-            PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
-            return false;
-          }
-        }
-        sample_values.Append((*view_its)[i].Get(1).AsLong());
-      }
-      gsample->set_value(sample_values);
+  bool AddSample(const protozero::PackedVarInt& values, int64_t callstack_id) {
+    const auto& frames = FramesForCallstack(callstack_id);
+    if (frames.empty()) {
+      PERFETTO_DFATAL_OR_ELOG(
+          "Failed to find frames for callstack id %" PRIi64 "", callstack_id);
+      return false;
     }
+    protozero::PackedVarInt location_ids;
+    for (int64_t frame : frames)
+      location_ids.Append(ToPprofId(frame));
+
+    auto* gsample = result_->add_sample();
+    gsample->set_value(values);
+    gsample->set_location_id(location_ids);
+
+    // remember frames to be emitted
+    seen_frames_.insert(frames.cbegin(), frames.cend());
+
     return true;
   }
 
+  std::string CompleteProfile(trace_processor::TraceProcessor* tp) {
+    std::set<int64_t> seen_mappings;
+    std::set<int64_t> seen_symbol_ids;
+
+    // Write the location info for frames referenced by the added samples.
+    if (!WriteFrames(tp, &seen_mappings, &seen_symbol_ids))
+      return {};
+    if (!WriteMappings(tp, seen_mappings))
+      return {};
+    if (!WriteSymbols(tp, seen_symbol_ids))
+      return {};
+    return result_.SerializeAsString();
+  }
+
+ private:
   bool WriteMappings(trace_processor::TraceProcessor* tp,
                      const std::set<int64_t>& seen_mappings) {
     Iterator mapping_it = tp->ExecuteQuery(
@@ -365,16 +320,16 @@
   }
 
   bool WriteFrames(trace_processor::TraceProcessor* tp,
-                   const std::set<int64_t>& seen_frames,
                    std::set<int64_t>* seen_mappings,
                    std::set<int64_t>* seen_symbol_ids) {
     Iterator frame_it = tp->ExecuteQuery(
-        "SELECT spf.id, spf.name, spf.mapping, spf.rel_pc, spf.symbol_set_id "
+        "SELECT spf.id, IFNULL(spf.deobfuscated_name, spf.name), spf.mapping, "
+        "spf.rel_pc, spf.symbol_set_id "
         "FROM stack_profile_frame spf;");
     size_t frames_no = 0;
     while (frame_it.Next()) {
       int64_t frame_id = frame_it.Get(0).AsLong();
-      if (seen_frames.find(frame_id) == seen_frames.end())
+      if (seen_frames_.find(frame_id) == seen_frames_.end())
         continue;
       frames_no++;
       std::string frame_name = frame_it.Get(1).AsString();
@@ -422,54 +377,13 @@
                               frame_it.Status().message().c_str());
       return false;
     }
-    if (frames_no != seen_frames.size()) {
+    if (frames_no != seen_frames_.size()) {
       PERFETTO_DFATAL_OR_ELOG("Missing frames.");
       return false;
     }
     return true;
   }
 
-  uint64_t ToPprofId(int64_t id) {
-    PERFETTO_DCHECK(id >= 0);
-    return static_cast<uint64_t>(id) + 1;
-  }
-
-  void WriteSampleTypes() {
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      Intern(kViews[i].type);
-      Intern(kViews[i].unit);
-    }
-
-    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
-      auto* sample_type = result_->add_sample_type();
-      sample_type->set_type(Intern(kViews[i].type));
-      sample_type->set_unit(Intern(kViews[i].unit));
-    }
-  }
-
-  std::string GenerateGProfile(trace_processor::TraceProcessor* tp,
-                               uint64_t upid,
-                               uint64_t ts,
-                               const char* heap_name) {
-    std::set<int64_t> seen_frames;
-    std::set<int64_t> seen_mappings;
-    std::set<int64_t> seen_symbol_ids;
-
-    std::vector<Iterator> view_its =
-        BuildViewIterators(tp, upid, ts, heap_name);
-
-    WriteSampleTypes();
-    if (!WriteAllocations(&view_its, &seen_frames))
-      return {};
-    if (!WriteFrames(tp, seen_frames, &seen_mappings, &seen_symbol_ids))
-      return {};
-    if (!WriteMappings(tp, seen_mappings))
-      return {};
-    if (!WriteSymbols(tp, seen_symbol_ids))
-      return {};
-    return result_.SerializeAsString();
-  }
-
   const std::vector<int64_t>& FramesForCallstack(int64_t callstack_id) {
     size_t callsite_idx = static_cast<size_t>(callstack_id);
     PERFETTO_CHECK(callstack_id >= 0 &&
@@ -494,7 +408,6 @@
     return it->second;
   }
 
- private:
   protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
       result_;
   std::map<std::string, int64_t> string_table_;
@@ -502,86 +415,183 @@
   const std::map<int64_t, std::vector<Line>>& symbol_set_id_to_lines_;
   const std::vector<Line> empty_line_vector_;
   int64_t max_symbol_id_;
+
+  std::set<int64_t> seen_frames_;
 };
 
 }  // namespace
 
-bool TraceToPprof(std::istream* input,
-                  std::vector<SerializedProfile>* output,
-                  profiling::Symbolizer* symbolizer,
-                  uint64_t pid,
-                  const std::vector<uint64_t>& timestamps) {
-  trace_processor::Config config;
-  std::unique_ptr<trace_processor::TraceProcessor> tp =
-      trace_processor::TraceProcessor::CreateInstance(config);
+namespace heap_profile {
+struct View {
+  const char* type;
+  const char* unit;
+  const char* aggregator;
+  const char* filter;
+};
+const View kSpaceView{"space", "bytes", "SUM(size)", nullptr};
+const View kAllocSpaceView{"alloc_space", "bytes", "SUM(size)", "size >= 0"};
+const View kAllocObjectsView{"alloc_objects", "count", "sum(count)",
+                             "size >= 0"};
+const View kObjectsView{"objects", "count", "SUM(count)", nullptr};
 
-  if (!ReadTrace(tp.get(), input))
-    return false;
+const View kViews[] = {kAllocObjectsView, kObjectsView, kAllocSpaceView,
+                       kSpaceView};
 
-  tp->NotifyEndOfFile();
-  return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
+static bool VerifyPIDStats(trace_processor::TraceProcessor* tp, uint64_t pid) {
+  bool success = true;
+  base::Optional<int64_t> stat =
+      GetStatsEntry(tp, "heapprofd_buffer_corrupted", base::make_optional(pid));
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_corrupted stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " ended early due to a buffer corruption."
+                  " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
+                  " CLIENT MEMORY CORRUPTION.",
+                  pid);
+  }
+  stat =
+      GetStatsEntry(tp, "heapprofd_buffer_overran", base::make_optional(pid));
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_buffer_overran stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " ended early due to a buffer overrun.",
+                  pid);
+  }
+
+  stat = GetStatsEntry(tp, "heapprofd_rejected_concurrent", pid);
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to get heapprofd_rejected_concurrent stat");
+  } else if (stat.value() > 0) {
+    success = false;
+    PERFETTO_ELOG("WARNING: The profile for %" PRIu64
+                  " was rejected due to a concurrent profile.",
+                  pid);
+  }
+  return success;
 }
 
-bool TraceToPprof(trace_processor::TraceProcessor* tp,
-                  std::vector<SerializedProfile>* output,
-                  profiling::Symbolizer* symbolizer,
-                  uint64_t pid,
-                  const std::vector<uint64_t>& timestamps) {
-  if (symbolizer) {
-    profiling::SymbolizeDatabase(
-        tp, symbolizer, [tp](const std::string& trace_proto) {
-          std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
-          memcpy(buf.get(), trace_proto.data(), trace_proto.size());
-          auto status = tp->Parse(std::move(buf), trace_proto.size());
-          if (!status.ok()) {
-            PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
-                                    status.message().c_str());
-            return;
-          }
-        });
+static std::vector<Iterator> BuildViewIterators(
+    trace_processor::TraceProcessor* tp,
+    uint64_t upid,
+    uint64_t ts,
+    const char* heap_name) {
+  std::vector<Iterator> view_its;
+  for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+    const View& v = kViews[i];
+    std::string query = "SELECT hpa.callsite_id ";
+    query +=
+        ", " + std::string(v.aggregator) + " FROM heap_profile_allocation hpa ";
+    // TODO(fmayer): Figure out where negative callsite_id comes from.
+    query += "WHERE hpa.callsite_id >= 0 ";
+    query += "AND hpa.upid = " + std::to_string(upid) + " ";
+    query += "AND hpa.ts <= " + std::to_string(ts) + " ";
+    query += "AND hpa.heap_name = '" + std::string(heap_name) + "' ";
+    if (v.filter)
+      query += "AND " + std::string(v.filter) + " ";
+    query += "GROUP BY hpa.callsite_id;";
+    view_its.emplace_back(tp->ExecuteQuery(query));
   }
+  return view_its;
+}
 
-  tp->NotifyEndOfFile();
-  auto max_symbol_id_it =
-      tp->ExecuteQuery("SELECT MAX(id) from stack_profile_symbol");
-  if (!max_symbol_id_it.Next()) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to get max symbol set id: %s",
-                            max_symbol_id_it.Status().message().c_str());
-    return false;
+static bool WriteAllocations(GProfileBuilder* builder,
+                             std::vector<Iterator>* view_its) {
+  for (;;) {
+    bool all_next = true;
+    bool any_next = false;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      Iterator& it = (*view_its)[i];
+      bool next = it.Next();
+      if (!it.Status().ok()) {
+        PERFETTO_DFATAL_OR_ELOG("Invalid view iterator: %s",
+                                it.Status().message().c_str());
+        return false;
+      }
+      all_next = all_next && next;
+      any_next = any_next || next;
+    }
+
+    if (!all_next) {
+      PERFETTO_CHECK(!any_next);
+      break;
+    }
+
+    protozero::PackedVarInt sample_values;
+    int64_t callstack_id = -1;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      if (i == 0) {
+        callstack_id = (*view_its)[i].Get(0).AsLong();
+      } else if (callstack_id != (*view_its)[i].Get(0).AsLong()) {
+        PERFETTO_DFATAL_OR_ELOG("Wrong callstack.");
+        return false;
+      }
+      sample_values.Append((*view_its)[i].Get(1).AsLong());
+    }
+
+    if (!builder->AddSample(sample_values, callstack_id))
+      return false;
   }
+  return true;
+}
 
-  int64_t max_symbol_id = max_symbol_id_it.Get(0).AsLong();
+static bool TraceToHeapPprof(trace_processor::TraceProcessor* tp,
+                             std::vector<SerializedProfile>* output,
+                             uint64_t target_pid,
+                             const std::vector<uint64_t>& target_timestamps) {
   const auto callsite_to_frames = GetCallsiteToFrames(tp);
   const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+  base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp);
+  if (!max_symbol_id.has_value())
+    return false;
 
   bool any_fail = false;
-  Iterator it = tp->ExecuteQuery(kQueryProfiles);
+  Iterator it = tp->ExecuteQuery(
+      "select distinct hpa.upid, hpa.ts, p.pid, hpa.heap_name "
+      "from heap_profile_allocation hpa, "
+      "process p where p.upid = hpa.upid;");
   while (it.Next()) {
     GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
-                            max_symbol_id);
+                            max_symbol_id.value());
     uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
     uint64_t ts = static_cast<uint64_t>(it.Get(1).AsLong());
     uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).AsLong());
     const char* heap_name = it.Get(3).AsString();
-    if ((pid > 0 && profile_pid != pid) ||
-        (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
-                                          ts) == timestamps.end())) {
+    if ((target_pid > 0 && profile_pid != target_pid) ||
+        (!target_timestamps.empty() &&
+         std::find(target_timestamps.begin(), target_timestamps.end(), ts) ==
+             target_timestamps.end())) {
       continue;
     }
 
-    if (!VerifyPIDStats(tp, pid))
+    if (!VerifyPIDStats(tp, profile_pid))
       any_fail = true;
 
-    std::string pid_query = "select pid from process where upid = ";
-    pid_query += std::to_string(upid) + ";";
-    Iterator pid_it = tp->ExecuteQuery(pid_query);
-    PERFETTO_CHECK(pid_it.Next());
+    std::vector<std::pair<std::string, std::string>> sample_types;
+    for (size_t i = 0; i < base::ArraySize(kViews); ++i) {
+      sample_types.emplace_back(std::string(kViews[i].type),
+                                std::string(kViews[i].unit));
+    }
+    builder.WriteSampleTypes(sample_types);
 
-    std::string profile_proto =
-        builder.GenerateGProfile(tp, upid, ts, heap_name);
+    std::vector<Iterator> view_its =
+        BuildViewIterators(tp, upid, ts, heap_name);
+    std::string profile_proto;
+    if (WriteAllocations(&builder, &view_its)) {
+      profile_proto = builder.CompleteProfile(tp);
+    }
     output->emplace_back(
-        SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).AsLong()),
-                          heap_name, profile_proto});
+        SerializedProfile{ProfileType::kHeapProfile, profile_pid,
+                          std::move(profile_proto), heap_name});
+  }
+
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+    return false;
   }
   if (any_fail) {
     PERFETTO_ELOG(
@@ -589,19 +599,148 @@
         "https://perfetto.dev/docs/data-sources/"
         "native-heap-profiler#troubleshooting");
   }
+  return true;
+}
+}  // namespace heap_profile
+
+namespace perf_profile {
+struct ProcessInfo {
+  uint64_t pid;
+  std::vector<uint64_t> utids;
+};
+
+// Returns a map of upid -> {pid, utids[]} for sampled processes.
+static std::map<uint64_t, ProcessInfo> GetProcessMap(
+    trace_processor::TraceProcessor* tp) {
+  Iterator it = tp->ExecuteQuery(
+      "select distinct process.upid, process.pid, thread.utid from perf_sample "
+      "join thread using (utid) join process using (upid) order by "
+      "process.upid asc");
+  std::map<uint64_t, ProcessInfo> process_map;
+  while (it.Next()) {
+    uint64_t upid = static_cast<uint64_t>(it.Get(0).AsLong());
+    uint64_t pid = static_cast<uint64_t>(it.Get(1).AsLong());
+    uint64_t utid = static_cast<uint64_t>(it.Get(2).AsLong());
+    process_map[upid].pid = pid;
+    process_map[upid].utids.push_back(utid);
+  }
   if (!it.Status().ok()) {
     PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
                             it.Status().message().c_str());
+    return {};
+  }
+  return process_map;
+}
+
+static void LogTracePerfEventIssues(trace_processor::TraceProcessor* tp) {
+  base::Optional<int64_t> stat = GetStatsEntry(tp, "perf_samples_skipped");
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to look up perf_samples_skipped stat");
+  } else if (stat.value() > 0) {
+    PERFETTO_ELOG(
+        "Warning: the trace recorded %" PRIi64
+        " skipped samples, which otherwise matched the tracing config. This "
+        "would cause a process to be completely absent from the trace, but "
+        "does *not* imply data loss in any of the output profiles.",
+        stat.value());
+  }
+
+  stat = GetStatsEntry(tp, "perf_samples_skipped_dataloss");
+  if (!stat.has_value()) {
+    PERFETTO_DFATAL_OR_ELOG(
+        "Failed to look up perf_samples_skipped_dataloss stat");
+  } else if (stat.value() > 0) {
+    PERFETTO_ELOG("DATA LOSS: the trace recorded %" PRIi64
+                  " lost perf samples (within traced_perf). This means that "
+                  "the trace is missing information, but it is not known "
+                  "which profile that affected.",
+                  stat.value());
+  }
+
+  // Check if any per-cpu ringbuffers encountered dataloss (as recorded by the
+  // kernel).
+  Iterator it = tp->ExecuteQuery(
+      "select idx, value from stats where name == 'perf_cpu_lost_records' and "
+      "value > 0 order by idx asc");
+  while (it.Next()) {
+    PERFETTO_ELOG(
+        "DATA LOSS: during the trace, the per-cpu kernel ring buffer for cpu "
+        "%" PRIi64 " recorded %" PRIi64
+        " lost samples. This means that the trace is missing information, "
+        "but it is not known which profile that affected.",
+        static_cast<int64_t>(it.Get(0).AsLong()),
+        static_cast<int64_t>(it.Get(1).AsLong()));
+  }
+  if (!it.Status().ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Invalid iterator: %s",
+                            it.Status().message().c_str());
+  }
+}
+
+// TODO(rsavitski): decide whether errors in |AddSample| should result in an
+// empty profile (and/or whether they should make the overall conversion
+// unsuccessful). Furthermore, clarify the return value's semantics for both
+// perf and heap profiles.
+static bool TraceToPerfPprof(trace_processor::TraceProcessor* tp,
+                             std::vector<SerializedProfile>* output,
+                             uint64_t target_pid) {
+  const auto callsite_to_frames = GetCallsiteToFrames(tp);
+  const auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
+  base::Optional<int64_t> max_symbol_id = GetMaxSymbolId(tp);
+  if (!max_symbol_id.has_value())
     return false;
+
+  LogTracePerfEventIssues(tp);
+
+  // Aggregate samples by upid when building profiles.
+  std::map<uint64_t, ProcessInfo> process_map = GetProcessMap(tp);
+  for (const auto& p : process_map) {
+    const ProcessInfo& process = p.second;
+
+    if (target_pid != 0 && process.pid != target_pid)
+      continue;
+
+    GProfileBuilder builder(callsite_to_frames, symbol_set_id_to_lines,
+                            max_symbol_id.value());
+
+    builder.WriteSampleTypes({{"samples", "count"}});
+
+    std::string query = "select callsite_id from perf_sample where utid in (" +
+                        AsCsvString(process.utids) + ") order by ts asc;";
+
+    protozero::PackedVarInt single_count_value;
+    single_count_value.Append(1);
+
+    Iterator it = tp->ExecuteQuery(query);
+    while (it.Next()) {
+      int64_t callsite_id = static_cast<int64_t>(it.Get(0).AsLong());
+      builder.AddSample(single_count_value, callsite_id);
+    }
+    if (!it.Status().ok()) {
+      PERFETTO_DFATAL_OR_ELOG("Failed to iterate over samples.");
+      return false;
+    }
+
+    std::string profile_proto = builder.CompleteProfile(tp);
+    output->emplace_back(SerializedProfile{
+        ProfileType::kPerfProfile, process.pid, std::move(profile_proto), ""});
   }
   return true;
 }
+}  // namespace perf_profile
 
-bool TraceToPprof(std::istream* input,
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
                   std::vector<SerializedProfile>* output,
+                  ConversionMode mode,
                   uint64_t pid,
                   const std::vector<uint64_t>& timestamps) {
-  return TraceToPprof(input, output, nullptr, pid, timestamps);
+  switch (mode) {
+    case (ConversionMode::kHeapProfile):
+      return heap_profile::TraceToHeapPprof(tp, output, pid, timestamps);
+    case (ConversionMode::kPerfProfile):
+      return perf_profile::TraceToPerfPprof(tp, output, pid);
+  }
+  PERFETTO_FATAL("unknown conversion option");  // for gcc
 }
 
 }  // namespace trace_to_text
diff --git a/tools/trace_to_text/trace_to_hprof.cc b/tools/trace_to_text/trace_to_hprof.cc
new file mode 100644
index 0000000..20e0573
--- /dev/null
+++ b/tools/trace_to_text/trace_to_hprof.cc
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tools/trace_to_text/trace_to_hprof.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/endian.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "tools/trace_to_text/utils.h"
+
+// Spec
+// http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#Basic_Type
+// Parser
+// https://cs.android.com/android/platform/superproject/+/master:art/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
+
+namespace perfetto {
+namespace trace_to_text {
+
+namespace {
+constexpr char kHeader[] = "PERFETTO_JAVA_HEAP";
+constexpr uint32_t kIdSz = 8;
+constexpr uint32_t kStackTraceSerialNumber = 1;
+
+class BigEndianBuffer {
+ public:
+  void WriteId(uint64_t val) { WriteU8(val); }
+
+  void WriteU8(uint64_t val) {
+    val = base::HostToBE64(val);
+    Write(reinterpret_cast<char*>(&val), sizeof(uint64_t));
+  }
+
+  void WriteU4(uint32_t val) {
+    val = base::HostToBE32(val);
+    Write(reinterpret_cast<char*>(&val), sizeof(uint32_t));
+  }
+
+  void SetU4(uint32_t val, size_t pos) {
+    val = base::HostToBE32(val);
+    PERFETTO_CHECK(pos + 4 <= buf_.size());
+    memcpy(buf_.data() + pos, &val, sizeof(uint32_t));
+  }
+
+  // Uncomment when needed
+  // void WriteU2(uint16_t val) {
+  //   val = base::HostToBE16(val);
+  //   Write(reinterpret_cast<char*>(&val), sizeof(uint16_t));
+  // }
+
+  void WriteByte(uint8_t val) { buf_.emplace_back(val); }
+
+  void Write(const char* val, uint32_t sz) {
+    const char* end = val + sz;
+    while (val < end) {
+      WriteByte(static_cast<uint8_t>(*val));
+      val++;
+    }
+  }
+
+  size_t written() const { return buf_.size(); }
+
+  void Flush(std::ostream* out) const {
+    out->write(buf_.data(), static_cast<std::streamsize>(buf_.size()));
+  }
+
+ private:
+  std::vector<char> buf_;
+};
+
+class HprofWriter {
+ public:
+  HprofWriter(std::ostream* output) : output_(output) {}
+
+  void WriteBuffer(const BigEndianBuffer& buf) { buf.Flush(output_); }
+
+  void WriteRecord(const uint8_t type,
+                   const std::function<void(BigEndianBuffer*)>&& writer) {
+    BigEndianBuffer buf;
+    buf.WriteByte(type);
+    // ts offset
+    buf.WriteU4(0);
+    // size placeholder
+    buf.WriteU4(0);
+    writer(&buf);
+    uint32_t record_sz = static_cast<uint32_t>(buf.written() - 9);
+    buf.SetU4(record_sz, 5);
+    WriteBuffer(buf);
+  }
+
+ private:
+  std::ostream* output_;
+};
+
+// A Class from the heap dump.
+class ClassData {
+ public:
+  explicit ClassData(uint64_t class_name_string_id)
+      : class_name_string_id_(class_name_string_id) {}
+
+  // Writes a HPROF LOAD_CLASS record for this Class
+  void WriteHprofLoadClass(HprofWriter* writer,
+                           uint64_t class_object_id,
+                           uint32_t class_serial_number) const {
+    writer->WriteRecord(0x02, [class_object_id, class_serial_number,
+                               this](BigEndianBuffer* buf) {
+      buf->WriteU4(class_serial_number);
+      buf->WriteId(class_object_id);
+      buf->WriteU4(kStackTraceSerialNumber);
+      buf->WriteId(class_name_string_id_);
+    });
+  }
+
+ private:
+  uint64_t class_name_string_id_;
+};
+
+// Ingested data from a Java Heap Profile for a name, location pair.
+// We need to support multiple class datas per pair as name, location is
+// not unique. Classloader should guarantee uniqueness but is not available
+// until S.
+class RawClassData {
+ public:
+  void AddClass(uint64_t id, base::Optional<uint64_t> superclass_id) {
+    ids_.push_back(std::make_pair(id, superclass_id));
+  }
+
+  void AddTemplate(uint64_t template_id) {
+    template_ids_.push_back(template_id);
+  }
+
+  // Transforms the raw data into one or more ClassData and adds them to the
+  // parameter map.
+  void ToClassData(std::unordered_map<uint64_t, ClassData>* id_to_class,
+                   uint64_t class_name_string_id) const {
+    // TODO(dinoderek) assert the two vectors have same length, iterate on both
+    for (auto it_ids = ids_.begin(); it_ids != ids_.end(); ++it_ids) {
+      // TODO(dinoderek) more data will be needed to write CLASS_DUMP
+      id_to_class->emplace(it_ids->first, ClassData(class_name_string_id));
+    }
+  }
+
+ private:
+  // Pair contains class ID and super class ID.
+  std::vector<std::pair<uint64_t, base::Optional<uint64_t>>> ids_;
+  // Class id of the template
+  std::vector<uint64_t> template_ids_;
+};
+
+// The Heap Dump data
+class HeapDump {
+ public:
+  explicit HeapDump(trace_processor::TraceProcessor* tp) : tp_(tp) {}
+
+  void Ingest() { IngestClasses(); }
+
+  void Write(HprofWriter* writer) {
+    WriteStrings(writer);
+    WriteLoadClass(writer);
+  }
+
+ private:
+  trace_processor::TraceProcessor* tp_;
+
+  // String IDs start from 1 as 0 appears to be reserved.
+  uint64_t next_string_id_ = 1;
+  // Strings to corresponding String ID
+  std::unordered_map<std::string, uint64_t> string_to_id_;
+  // Type ID to corresponding Class
+  std::unordered_map<uint64_t, ClassData> id_to_class_;
+
+  // Ingests and processes the class data from the heap dump.
+  void IngestClasses() {
+    // TODO(dinoderek): heap_graph_class does not support pid or ts filtering
+
+    std::map<std::pair<uint64_t, std::string>, RawClassData> raw_classes;
+
+    auto it = tp_->ExecuteQuery(R"(SELECT
+          id,
+          IFNULL(deobfuscated_name, name),
+          superclass_id,
+          location
+        FROM heap_graph_class )");
+
+    while (it.Next()) {
+      uint64_t id = static_cast<uint64_t>(it.Get(0).AsLong());
+
+      std::string raw_dname(it.Get(1).AsString());
+      std::string dname;
+      bool is_template_class =
+          base::StartsWith(raw_dname, std::string("java.lang.Class<"));
+      if (is_template_class) {
+        dname = raw_dname.substr(17, raw_dname.size() - 18);
+      } else {
+        dname = raw_dname;
+      }
+      uint64_t name_id = IngestString(dname);
+
+      auto raw_super_id = it.Get(2);
+      base::Optional<uint64_t> maybe_super_id =
+          raw_super_id.is_null()
+              ? base::nullopt
+              : base::Optional<uint64_t>(
+                    static_cast<uint64_t>(raw_super_id.AsLong()));
+
+      std::string location(it.Get(3).AsString());
+
+      auto raw_classes_it =
+          raw_classes.emplace(std::make_pair(name_id, location), RawClassData())
+              .first;
+      if (is_template_class) {
+        raw_classes_it->second.AddTemplate(id);
+      } else {
+        raw_classes_it->second.AddClass(id, maybe_super_id);
+      }
+    }
+
+    for (const auto& raw : raw_classes) {
+      auto class_name_string_id = raw.first.first;
+      raw.second.ToClassData(&id_to_class_, class_name_string_id);
+    }
+  }
+
+  // Ingests the parameter string and returns the HPROF ID for the string.
+  uint64_t IngestString(const std::string& s) {
+    auto maybe_id = string_to_id_.find(s);
+    if (maybe_id != string_to_id_.end()) {
+      return maybe_id->second;
+    } else {
+      auto id = next_string_id_;
+      next_string_id_ += 1;
+      string_to_id_[s] = id;
+      return id;
+    }
+  }
+
+  // Writes STRING sections to the output
+  void WriteStrings(HprofWriter* writer) {
+    for (const auto& it : string_to_id_) {
+      writer->WriteRecord(0x01, [it](BigEndianBuffer* buf) {
+        buf->WriteId(it.second);
+        // TODO(dinoderek): UTF-8 encoding
+        buf->Write(it.first.c_str(), static_cast<uint32_t>(it.first.length()));
+      });
+    }
+  }
+
+  // Writes LOAD CLASS sections to the output
+  void WriteLoadClass(HprofWriter* writer) {
+    uint32_t class_serial_number = 1;
+    for (const auto& it : id_to_class_) {
+      it.second.WriteHprofLoadClass(writer, it.first, class_serial_number);
+      class_serial_number += 1;
+    }
+  }
+};
+
+void WriteHeaderAndStack(HprofWriter* writer) {
+  BigEndianBuffer header;
+  header.Write(kHeader, sizeof(kHeader));
+  // Identifier size
+  header.WriteU4(kIdSz);
+  // walltime high (unused)
+  header.WriteU4(0);
+  // walltime low (unused)
+  header.WriteU4(0);
+  writer->WriteBuffer(header);
+
+  // Add placeholder stack trace (required by the format).
+  writer->WriteRecord(0x05, [](BigEndianBuffer* buf) {
+    buf->WriteU4(kStackTraceSerialNumber);
+    buf->WriteU4(0);
+    buf->WriteU4(0);
+  });
+}
+}  // namespace
+
+int TraceToHprof(trace_processor::TraceProcessor* tp,
+                 std::ostream* output,
+                 uint64_t pid,
+                 uint64_t ts) {
+  PERFETTO_DCHECK(tp != nullptr && pid != 0 && ts != 0);
+
+  HprofWriter writer(output);
+  HeapDump dump(tp);
+
+  dump.Ingest();
+  WriteHeaderAndStack(&writer);
+  dump.Write(&writer);
+
+  return 0;
+}
+
+int TraceToHprof(std::istream* input,
+                 std::ostream* output,
+                 uint64_t pid,
+                 std::vector<uint64_t> timestamps) {
+  // TODO: Simplify this for cmdline users. For example, if there is a single
+  // heap graph, use this, and only fail when there is ambiguity.
+  if (pid == 0) {
+    PERFETTO_ELOG("Must specify pid");
+    return -1;
+  }
+  if (timestamps.size() != 1) {
+    PERFETTO_ELOG("Must specify single timestamp");
+    return -1;
+  }
+  trace_processor::Config config;
+  std::unique_ptr<trace_processor::TraceProcessor> tp =
+      trace_processor::TraceProcessor::CreateInstance(config);
+  if (!ReadTrace(tp.get(), input))
+    return false;
+  tp->NotifyEndOfFile();
+  return TraceToHprof(tp.get(), output, pid, timestamps[0]);
+}
+
+}  // namespace trace_to_text
+}  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_hprof.h b/tools/trace_to_text/trace_to_hprof.h
new file mode 100644
index 0000000..acc142a
--- /dev/null
+++ b/tools/trace_to_text/trace_to_hprof.h
@@ -0,0 +1,40 @@
+/*
+ * 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 TOOLS_TRACE_TO_TEXT_TRACE_TO_HPROF_H_
+#define TOOLS_TRACE_TO_TEXT_TRACE_TO_HPROF_H_
+
+#include <iostream>
+#include <vector>
+#include "perfetto/trace_processor/trace_processor.h"
+
+namespace perfetto {
+namespace trace_to_text {
+
+int TraceToHprof(trace_processor::TraceProcessor* tp,
+                 std::ostream* output,
+                 uint64_t pid,
+                 uint64_t timestamp);
+
+int TraceToHprof(std::istream* input,
+                 std::ostream* output,
+                 uint64_t pid = 0,
+                 std::vector<uint64_t> timestamps = {});
+
+}  // namespace trace_to_text
+}  // namespace perfetto
+
+#endif  // TOOLS_TRACE_TO_TEXT_TRACE_TO_HPROF_H_
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index 9b70928..5a5d261 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -19,13 +19,13 @@
 #include <string>
 #include <vector>
 
-#include "perfetto/base/build_config.h"
-
-#include "src/profiling/symbolizer/symbolize_database.h"
+#include "perfetto/trace_processor/trace_processor.h"
 #include "src/profiling/symbolizer/local_symbolizer.h"
+#include "src/profiling/symbolizer/symbolize_database.h"
 #include "tools/trace_to_text/utils.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
 #include "perfetto/ext/base/utils.h"
@@ -33,42 +33,77 @@
 #include "src/profiling/symbolizer/symbolizer.h"
 
 namespace {
-
 constexpr const char* kDefaultTmp = "/tmp";
 
 std::string GetTemp() {
-  const char* tmp = getenv("TMPDIR");
-  if (tmp == nullptr)
-    tmp = kDefaultTmp;
-  return tmp;
+  const char* tmp = nullptr;
+  if ((tmp = getenv("TMPDIR")))
+    return tmp;
+  if ((tmp = getenv("TEMP")))
+    return tmp;
+  return kDefaultTmp;
 }
-
 }  // namespace
 
 namespace perfetto {
 namespace trace_to_text {
+namespace {
 
-int TraceToProfile(std::istream* input,
-                   std::ostream* output,
-                   uint64_t pid,
-                   std::vector<uint64_t> timestamps) {
+void MaybeSymbolize(trace_processor::TraceProcessor* tp) {
   std::unique_ptr<profiling::Symbolizer> symbolizer =
       profiling::LocalSymbolizerOrDie(profiling::GetPerfettoBinaryPath(),
                                       getenv("PERFETTO_SYMBOLIZER_MODE"));
+  if (!symbolizer)
+    return;
+  profiling::SymbolizeDatabase(tp, symbolizer.get(),
+                               [tp](const std::string& trace_proto) {
+                                 IngestTraceOrDie(tp, trace_proto);
+                               });
+  tp->NotifyEndOfFile();
+}
 
+void MaybeDeobfuscate(trace_processor::TraceProcessor* tp) {
+  auto maybe_map = profiling::GetPerfettoProguardMapPath();
+  if (maybe_map.empty()) {
+    return;
+  }
+  profiling::ReadProguardMapsToDeobfuscationPackets(
+      maybe_map, [tp](const std::string& trace_proto) {
+        IngestTraceOrDie(tp, trace_proto);
+      });
+  tp->NotifyEndOfFile();
+}
+
+int TraceToProfile(
+    std::istream* input,
+    std::ostream* output,
+    uint64_t pid,
+    std::vector<uint64_t> timestamps,
+    ConversionMode conversion_mode,
+    std::string dirname_prefix,
+    std::function<std::string(const SerializedProfile&)> filename_fn) {
   std::vector<SerializedProfile> profiles;
-  TraceToPprof(input, &profiles, symbolizer.get(), pid, timestamps);
+  trace_processor::Config config;
+  std::unique_ptr<trace_processor::TraceProcessor> tp =
+      trace_processor::TraceProcessor::CreateInstance(config);
+
+  if (!ReadTrace(tp.get(), input))
+    return -1;
+
+  tp->NotifyEndOfFile();
+  MaybeSymbolize(tp.get());
+  MaybeDeobfuscate(tp.get());
+
+  TraceToPprof(tp.get(), &profiles, conversion_mode, pid, timestamps);
   if (profiles.empty()) {
     return 0;
   }
 
-  std::string temp_dir = GetTemp() + "/heap_profile-XXXXXXX";
-  PERFETTO_CHECK(mkdtemp(&temp_dir[0]));
-  size_t itr = 0;
+  std::string temp_dir =
+      GetTemp() + "/" + dirname_prefix + base::GetTimeFmt("%y%m%d%H%M%S");
+  PERFETTO_CHECK(base::Mkdir(temp_dir));
   for (const auto& profile : profiles) {
-    std::string filename = temp_dir + "/heap_dump." + std::to_string(++itr) +
-                           "." + std::to_string(profile.pid) + "." +
-                           profile.heap_name + ".pb";
+    std::string filename = temp_dir + "/" + filename_fn(profile);
     base::ScopedFile fd(base::OpenFile(filename, O_CREAT | O_WRONLY, 0700));
     if (!fd)
       PERFETTO_FATAL("Failed to open %s", filename.c_str());
@@ -80,5 +115,37 @@
   return 0;
 }
 
+}  // namespace
+
+int TraceToHeapProfile(std::istream* input,
+                       std::ostream* output,
+                       uint64_t pid,
+                       std::vector<uint64_t> timestamps) {
+  int file_idx = 0;
+  auto filename_fn = [&file_idx](const SerializedProfile& profile) {
+    return "heap_dump." + std::to_string(++file_idx) + "." +
+           std::to_string(profile.pid) + "." + profile.heap_name + ".pb";
+  };
+
+  return TraceToProfile(input, output, pid, timestamps,
+                        ConversionMode::kHeapProfile, "heap_profile-",
+                        filename_fn);
+}
+
+int TraceToPerfProfile(std::istream* input,
+                       std::ostream* output,
+                       uint64_t pid,
+                       std::vector<uint64_t> timestamps) {
+  int file_idx = 0;
+  auto filename_fn = [&file_idx](const SerializedProfile& profile) {
+    return "profile." + std::to_string(++file_idx) + ".pid." +
+           std::to_string(profile.pid) + ".pb";
+  };
+
+  return TraceToProfile(input, output, pid, timestamps,
+                        ConversionMode::kPerfProfile, "perf_profile-",
+                        filename_fn);
+}
+
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_profile.h b/tools/trace_to_text/trace_to_profile.h
index 629d3ef..1c41aad 100644
--- a/tools/trace_to_text/trace_to_profile.h
+++ b/tools/trace_to_text/trace_to_profile.h
@@ -23,10 +23,17 @@
 namespace perfetto {
 namespace trace_to_text {
 
-int TraceToProfile(std::istream* input,
-                   std::ostream* output,
-                   uint64_t pid = 0,
-                   std::vector<uint64_t> timestamps = {});
+// 0: success
+int TraceToHeapProfile(std::istream* input,
+                       std::ostream* output,
+                       uint64_t pid,
+                       std::vector<uint64_t> timestamps);
+
+// 0: success
+int TraceToPerfProfile(std::istream* input,
+                       std::ostream* output,
+                       uint64_t pid,
+                       std::vector<uint64_t> timestamps);
 
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/trace_to_text.cc b/tools/trace_to_text/trace_to_text.cc
index 1616a2d..2fef5a5 100644
--- a/tools/trace_to_text/trace_to_text.cc
+++ b/tools/trace_to_text/trace_to_text.cc
@@ -22,6 +22,7 @@
 #include <google/protobuf/text_format.h>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "tools/trace_to_text/proto_full_utils.h"
 #include "tools/trace_to_text/utils.h"
diff --git a/tools/trace_to_text/utils.cc b/tools/trace_to_text/utils.cc
index f85a421..dcacd5e 100644
--- a/tools/trace_to_text/utils.cc
+++ b/tools/trace_to_text/utils.cc
@@ -25,9 +25,14 @@
 #include <utility>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/trace_processor/trace_processor.h"
 
+#include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
 #include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
@@ -43,51 +48,11 @@
 constexpr size_t kCompressionBufferSize = 500 * 1024;
 #endif
 
-std::map<std::string, std::set<std::string>> GetHeapGraphClasses(
-    trace_processor::TraceProcessor* tp) {
-  std::map<std::string, std::set<std::string>> res;
-  Iterator it = tp->ExecuteQuery("select type_name from heap_graph_object");
-  while (it.Next())
-    res.emplace(it.Get(0).string_value, std::set<std::string>());
-
-  PERFETTO_CHECK(it.Status().ok());
-
-  it = tp->ExecuteQuery("select field_name from heap_graph_reference");
-  while (it.Next()) {
-    std::string field_name = it.Get(0).string_value;
-    if (field_name.size() == 0)
-      continue;
-    size_t n = field_name.rfind(".");
-    if (n == std::string::npos || n == field_name.size() - 1) {
-      PERFETTO_ELOG("Invalid field name: %s", field_name.c_str());
-      continue;
-    }
-    std::string class_name = field_name;
-    class_name.resize(n);
-    field_name = field_name.substr(n + 1);
-    res[class_name].emplace(field_name);
-  }
-
-  PERFETTO_CHECK(it.Status().ok());
-  return res;
-}
-
-using ::protozero::proto_utils::kMessageLengthFieldSize;
 using ::protozero::proto_utils::MakeTagLengthDelimited;
 using ::protozero::proto_utils::WriteVarInt;
 
 }  // namespace
 
-void WriteTracePacket(const std::string& str, std::ostream* output) {
-  constexpr char kPreamble =
-      MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
-  uint8_t length_field[10];
-  uint8_t* end = WriteVarInt(str.size(), length_field);
-  *output << kPreamble;
-  *output << std::string(length_field, end);
-  *output << str;
-}
-
 void ForEachPacketBlobInTrace(
     std::istream* input,
     const std::function<void(std::unique_ptr<char[]>, size_t)>& f) {
@@ -133,13 +98,6 @@
   }
 }
 
-base::Optional<std::string> GetPerfettoProguardMapPath() {
-  base::Optional<std::string> proguard_map;
-  const char* env = getenv("PERFETTO_PROGUARD_MAP");
-  if (env != nullptr)
-    proguard_map = env;
-  return proguard_map;
-}
 
 bool ReadTrace(trace_processor::TraceProcessor* tp, std::istream* input) {
   // 1MB chunk size seems the best tradeoff on a MacBook Pro 2013 - i7 2.8 GHz.
@@ -180,51 +138,21 @@
   return true;
 }
 
-void DeobfuscateDatabase(
-    trace_processor::TraceProcessor* tp,
-    const std::map<std::string, profiling::ObfuscatedClass>& mapping,
-    std::function<void(const std::string&)> callback) {
-  std::map<std::string, std::set<std::string>> classes =
-      GetHeapGraphClasses(tp);
-  protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket> packet;
-  // TODO(fmayer): Add handling for package name and version code here so we
-  // can support multiple dumps in the same trace.
-  auto* proto_mapping = packet->set_deobfuscation_mapping();
-  for (const auto& p : classes) {
-    std::string obfuscated_class_name = p.first;
-    while (obfuscated_class_name.size() > 2 &&
-           obfuscated_class_name.substr(obfuscated_class_name.size() - 2) ==
-               "[]") {
-      obfuscated_class_name.resize(obfuscated_class_name.size() - 2);
-    }
-    const std::set<std::string>& obfuscated_field_names = p.second;
-    auto it = mapping.find(obfuscated_class_name);
-    if (it == mapping.end()) {
-      // This can happen for non-obfuscated class names. Do not log.
-      continue;
-    }
-    const profiling::ObfuscatedClass& cls = it->second;
-    auto* proto_class = proto_mapping->add_obfuscated_classes();
-    proto_class->set_obfuscated_name(obfuscated_class_name);
-    proto_class->set_deobfuscated_name(cls.deobfuscated_name);
-    for (const std::string& obfuscated_field_name : obfuscated_field_names) {
-      auto field_it = cls.deobfuscated_fields.find(obfuscated_field_name);
-      if (field_it != cls.deobfuscated_fields.end()) {
-        auto* proto_member = proto_class->add_obfuscated_members();
-        proto_member->set_obfuscated_name(obfuscated_field_name);
-        proto_member->set_deobfuscated_name(field_it->second);
-      } else {
-        PERFETTO_ELOG("%s.%s not found", obfuscated_class_name.c_str(),
-                      obfuscated_field_name.c_str());
-      }
-    }
+void IngestTraceOrDie(trace_processor::TraceProcessor* tp,
+                      const std::string& trace_proto) {
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
+  memcpy(buf.get(), trace_proto.data(), trace_proto.size());
+  auto status = tp->Parse(std::move(buf), trace_proto.size());
+  if (!status.ok()) {
+    PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s", status.message().c_str());
   }
-  callback(packet.SerializeAsString());
 }
 
 TraceWriter::TraceWriter(std::ostream* output) : output_(output) {}
 
-TraceWriter::~TraceWriter() = default;
+TraceWriter::~TraceWriter() {
+  output_->flush();
+}
 
 void TraceWriter::Write(const std::string& s) {
   Write(s.data(), s.size());
diff --git a/tools/trace_to_text/utils.h b/tools/trace_to_text/utils.h
index e027ad1..1ac1d6d 100644
--- a/tools/trace_to_text/utils.h
+++ b/tools/trace_to_text/utils.h
@@ -18,8 +18,6 @@
 #define TOOLS_TRACE_TO_TEXT_UTILS_H_
 
 #include <stdio.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
 
 #include <functional>
 #include <iostream>
@@ -59,19 +57,10 @@
     std::istream* input,
     const std::function<void(std::unique_ptr<char[]>, size_t)>&);
 
-base::Optional<std::string> GetPerfettoProguardMapPath();
 
 bool ReadTrace(trace_processor::TraceProcessor* tp, std::istream* input);
-
-void WriteTracePacket(const std::string& str, std::ostream* output);
-
-// Generate ObfuscationMapping protos for all obfuscated java names in the
-// database.
-// Wrap them in proto-encoded TracePackets messages and call callback.
-void DeobfuscateDatabase(
-    trace_processor::TraceProcessor* tp,
-    const std::map<std::string, profiling::ObfuscatedClass>& mapping,
-    std::function<void(const std::string&)> callback);
+void IngestTraceOrDie(trace_processor::TraceProcessor* tp,
+                      const std::string& trace_proto);
 
 class TraceWriter {
  public:
diff --git a/tools/traceconv b/tools/traceconv
index 91fb1bb..f1bcf41 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -32,8 +32,8 @@
 
 
 TRACE_TO_TEXT_SHAS = {
-    'linux': '2c0a434c44331bd53ff9641a7d1e467ff91ef043',
-    'mac': 'b0c0ee6e4fd4133562bcf0809108b4e031240b7f',
+    'linux': '0df4f571099135a29659caf35fd5e343ef59bdc3',
+    'mac': '71b86dfd10409fd9ad9c41cc06cd8533bd2ffa85',
 }
 TRACE_TO_TEXT_PATH = tempfile.gettempdir()
 TRACE_TO_TEXT_BASE_URL = ('https://storage.googleapis.com/perfetto/')
diff --git a/tools/update_trace_processor b/tools/update_trace_processor
index 6601ca9..3968efa 100755
--- a/tools/update_trace_processor
+++ b/tools/update_trace_processor
@@ -19,16 +19,17 @@
 tools/gn gen $DIR --args='is_clang=true is_debug=false'
 tools/ninja -C $DIR trace_processor_shell
 
-if which shasum; then
-  NEW_SHA=$(shasum $DIR/trace_processor_shell | cut -f1 -d' ') # Mac OS
-else
-  NEW_SHA=$(sha1sum $DIR/trace_processor_shell | cut -f1 -d' ') # Linux
-fi
-
 if is_mac; then
   platform=mac
 else
   platform=linux
+  strip $DIR/trace_processor_shell
+fi
+
+if which shasum; then
+  NEW_SHA=$(shasum $DIR/trace_processor_shell | cut -f1 -d' ') # Mac OS
+else
+  NEW_SHA=$(sha1sum $DIR/trace_processor_shell | cut -f1 -d' ') # Linux
 fi
 
 name=trace_processor_shell-$platform-$NEW_SHA
diff --git a/tools/update_traceconv b/tools/update_traceconv
index 2860273..e7ee66b 100755
--- a/tools/update_traceconv
+++ b/tools/update_traceconv
@@ -19,16 +19,17 @@
 tools/gn gen $DIR --args='is_clang=true is_debug=false'
 tools/ninja -C $DIR trace_to_text
 
-if which shasum; then
-  NEW_SHA=$(shasum $DIR/trace_to_text | cut -f1 -d' ') # Mac OS
-else
-  NEW_SHA=$(sha1sum $DIR/trace_to_text | cut -f1 -d' ') # Linux
-fi
-
 if is_mac; then
   platform=mac
 else
   platform=linux
+  strip $DIR/trace_to_text
+fi
+
+if which shasum; then
+  NEW_SHA=$(shasum $DIR/trace_to_text | cut -f1 -d' ') # Mac OS
+else
+  NEW_SHA=$(sha1sum $DIR/trace_to_text | cut -f1 -d' ') # Linux
 fi
 
 name=trace_to_text-$platform-$NEW_SHA
diff --git a/tools/write_version_header.py b/tools/write_version_header.py
new file mode 100755
index 0000000..88dcb30
--- /dev/null
+++ b/tools/write_version_header.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+# 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.
+"""
+Writes the perfetto_version{.gen.h, .ts} files.
+
+This tool is run as part of a genrule from GN, SoonG and Bazel builds. It
+generates a source header (or in the case of --ts_out a TypeScript file) that
+contains:
+- The version number (e.g. v9.0) obtained parsing the CHANGELOG file.
+- The git HEAD's commit-ish (e.g. 6b330b772b0e973f79c70ba2e9bb2b0110c6715d)
+- The number of CLs from the release tag to HEAD.
+
+The latter is concatenated to the version number to distinguish builds made
+fully from release tags (e.g., v9.0.0) vs builds made from the main branch which
+are N cls ahead of the latest monthly release (e.g., v9.0.42).
+"""
+
+import argparse
+import os
+import re
+import sys
+import subprocess
+
+# Note: PROJECT_ROOT is not accurate in bazel builds, where this script is
+# executed in the bazel sandbox.
+PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+SCM_REV_NOT_AVAILABLE = 'N/A'
+
+
+def get_latest_release(changelog_path):
+  """Returns a string like 'v9.0'.
+
+  It does so by searching the latest version mentioned in the CHANGELOG."""
+  if not changelog_path:
+    if os.path.exists('CHANGELOG'):
+      changelog_path = 'CHANGELOG'
+    else:
+      changelog_path = os.path.join(PROJECT_ROOT, 'CHANGELOG')
+  with open(changelog_path) as f:
+    for line in f.readlines():
+      m = re.match('^(v\d+[.]\d+)\s.*$', line)
+      if m is not None:
+        return m.group(1)
+  raise Exception('Failed to fetch Perfetto version from %s' % changelog_path)
+
+
+def get_git_info(last_release_tag):
+  """Returns a tuple ('deadbeef', '1234').
+
+  The first value is the SHA1 of the HEAD. The second is the number of CLs from
+  the passed |last_release_tag| to HEAD."""
+  commit_sha1 = SCM_REV_NOT_AVAILABLE
+  commits_since_release = ''
+  git_dir = os.path.join(PROJECT_ROOT, '.git')
+  if os.path.exists(git_dir):
+    try:
+      commit_sha1 = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
+                                            cwd=PROJECT_ROOT).strip().decode()
+      with open(os.devnull, 'wb') as devnull:
+        commits_since_release = subprocess.check_output(
+            [
+                'git', 'rev-list', '--count',
+                'refs/tags/%s..HEAD' % last_release_tag
+            ],
+            cwd=PROJECT_ROOT,
+            stderr=devnull).strip().decode()
+    except subprocess.CalledProcessError:
+      pass
+
+  return (commit_sha1, commits_since_release)
+
+
+def write_if_unchanged(path, content):
+  prev_content = None
+  if os.path.exists(path):
+    with open(path, 'r') as fprev:
+      prev_content = fprev.read()
+  if prev_content == content:
+    return 0
+  with open(path, 'w') as fout:
+    fout.write(content)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--no_git',
+      action='store_true',
+      help='Skips running git rev-parse, emits only the version from CHANGELOG')
+  parser.add_argument('--cpp_out', help='Path of the generated .h file.')
+  parser.add_argument('--ts_out', help='Path of the generated .ts file.')
+  parser.add_argument('--changelog', help='Path to CHANGELOG.')
+  args = parser.parse_args()
+
+  release = get_latest_release(args.changelog)
+  if args.no_git:
+    git_sha1, commits_since_release = (SCM_REV_NOT_AVAILABLE, '')
+  else:
+    git_sha1, commits_since_release = get_git_info(release)
+
+  # Try to compute the number of commits since the last release. This can fail
+  # in some environments (e.g. in android builds) because the bots pull only
+  # the main branch and don't pull the whole list of tags.
+  if commits_since_release:
+    version = '%s.%s' % (release, commits_since_release)  # e.g., 'v9.0.42'.
+  else:
+    version = release  # e.g., 'v9.0'.
+
+  if args.cpp_out:
+    guard = '%s_' % args.cpp_out.upper()
+    guard = re.sub(r'[^\w]', '_', guard)
+    lines = []
+    lines.append('// Generated by %s' % __file__)
+    lines.append('')
+    lines.append('#ifndef %s' % guard)
+    lines.append('#define %s' % guard)
+    lines.append('')
+    lines.append('#define PERFETTO_VERSION_STRING() "%s"' % version)
+    lines.append('#define PERFETTO_VERSION_SCM_REVISION() "%s"' % git_sha1)
+    lines.append('')
+    lines.append('#endif  // %s' % guard)
+    lines.append('')
+    content = '\n'.join(lines)
+    write_if_unchanged(args.cpp_out, content)
+
+  if args.ts_out:
+    lines = []
+    lines.append('export const VERSION = "%s";' % version)
+    lines.append('export const SCM_REVISION = "%s";' % git_sha1)
+    content = '\n'.join(lines)
+    write_if_unchanged(args.ts_out, content)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/ui/BUILD.gn b/ui/BUILD.gn
index 738158e..2aec99a 100644
--- a/ui/BUILD.gn
+++ b/ui/BUILD.gn
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("../gn/gen_perfetto_version_header.gni")
 import("../gn/perfetto.gni")
 import("../gn/perfetto_check_build_deps.gni")
 import("../gn/wasm.gni")
@@ -99,7 +100,16 @@
     if (defined(invoker.suppress_stderr) && invoker.suppress_stderr) {
       args += [ "--suppress_stderr" ]
     }
+
+    # Some of the node_bin rules *cough*transpile_all_ts*cough* don't
+    # accuratly report the output files. This means if they can run,
+    # change something, and not cause their dependees to rerun causing
+    # bugs. Adding a stamp file which is always rewritten avoids this.
+    # See also b/120010518
+    stamp_path = "$ui_dir/$target_name.node_bin.stamp"
+    outputs += [ stamp_path ]
     args += [
+              "--stamp=" + rebase_path(stamp_path, root_build_dir),
               "--path=$nodejs_bin",
               "node",
               rebase_path("node_modules/.bin/$_node_cmd", root_build_dir),
@@ -260,6 +270,7 @@
   deps = [
     ":dist_symlink",
     ":protos_to_ts",
+    ":version_ts_gen",
     ":wasm_gen",
   ]
   inputs = [ "tsconfig.json" ]
@@ -286,6 +297,7 @@
 
   node_cmd = "tsc"
   args = [
+    "--incremental",
     "--project",
     rebase_path(".", root_build_dir),
     "--outDir",
@@ -322,6 +334,7 @@
   "src/assets/analyze_page.scss",
   "src/assets/common.scss",
   "src/assets/details.scss",
+  "src/assets/metrics_page.scss",
   "src/assets/modal.scss",
   "src/assets/record.scss",
   "src/assets/sidebar.scss",
@@ -382,8 +395,8 @@
               "src/assets/rec_cpu_fine.png",
               "src/assets/rec_cpu_freq.png",
               "src/assets/rec_cpu_voltage.png",
-              "src/assets/rec_cpu_wakeup.png",
               "src/assets/rec_ftrace.png",
+              "src/assets/rec_gpu_mem_total.png",
               "src/assets/rec_java_heap_dump.png",
               "src/assets/rec_lmk.png",
               "src/assets/rec_logcat.png",
@@ -565,7 +578,7 @@
 
 # This target generates an map containing all the UI subresources and their
 # hashes. This is used by the service worker code for offline caching.
-# This taarget needs to be kept at the end of the BUILD.gn file, because of the
+# This target needs to be kept at the end of the BUILD.gn file, because of the
 # get_target_outputs() call (fails otherwise due to GN's evaluation order).
 action("gen_dist_file_map") {
   out_file_path = "$ui_gen_dir/dist_file_map.ts"
@@ -587,3 +600,7 @@
            rebase_path(ui_dir, root_build_dir),
          ] + dist_files
 }
+
+gen_perfetto_version_header("version_ts_gen") {
+  ts_out = "$ui_gen_dir/perfetto_version.ts"
+}
diff --git a/ui/index.html b/ui/index.html
index a5dbde0..a47de38 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -4,10 +4,16 @@
   <title>Perfetto UI</title>
   <!-- See b/149573396 for CSP rationale. -->
   <!-- TODO(b/121211019): remove script-src-elem rule once fixed. -->
-  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src-elem 'self' https://*.google.com https://*.googleusercontent.com; object-src 'none'; connect-src 'self' http://127.0.0.1:9001 https://*.googleapis.com; navigate-to https://*.perfetto.dev;">
+  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src-elem 'self' https://*.google.com https://*.googleusercontent.com https://www.googletagmanager.com https://www.google-analytics.com 'sha256-eYlPNiizBKy/rhHAaz06RXrXVsKmBN6tTFYwmJTvcwc='; object-src 'none'; connect-src 'self' http://127.0.0.1:9001 https://www.google-analytics.com https://*.googleapis.com blob: data:; img-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; navigate-to https://*.perfetto.dev;">
   <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
   <link href="perfetto.css" rel="stylesheet">
   <link rel="icon" type="image/png" href="assets/favicon.png">
+  <!-- Global site tag (gtag.js) - Google Analytics -->
+  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-137828855-1"></script>
+  <script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag(){dataLayer.push(arguments);}
+  </script>
 </head>
 <body>
   <main>
diff --git a/ui/package-lock.json b/ui/package-lock.json
index c97de68..8613d3f 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -14,45 +14,45 @@
       }
     },
     "@babel/core": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.1.tgz",
-      "integrity": "sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz",
+      "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.10.1",
-        "@babel/generator": "^7.10.1",
-        "@babel/helper-module-transforms": "^7.10.1",
-        "@babel/helpers": "^7.10.1",
-        "@babel/parser": "^7.10.1",
-        "@babel/template": "^7.10.1",
-        "@babel/traverse": "^7.10.1",
-        "@babel/types": "^7.10.1",
+        "@babel/code-frame": "^7.10.4",
+        "@babel/generator": "^7.10.5",
+        "@babel/helper-module-transforms": "^7.10.5",
+        "@babel/helpers": "^7.10.4",
+        "@babel/parser": "^7.10.5",
+        "@babel/template": "^7.10.4",
+        "@babel/traverse": "^7.10.5",
+        "@babel/types": "^7.10.5",
         "convert-source-map": "^1.7.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.1",
         "json5": "^2.1.2",
-        "lodash": "^4.17.13",
+        "lodash": "^4.17.19",
         "resolve": "^1.3.2",
         "semver": "^5.4.1",
         "source-map": "^0.5.0"
       },
       "dependencies": {
         "@babel/code-frame": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
-          "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+          "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
           "dev": true,
           "requires": {
-            "@babel/highlight": "^7.10.1"
+            "@babel/highlight": "^7.10.4"
           }
         },
         "@babel/highlight": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
-          "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+          "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
           "dev": true,
           "requires": {
-            "@babel/helper-validator-identifier": "^7.10.1",
+            "@babel/helper-validator-identifier": "^7.10.4",
             "chalk": "^2.0.0",
             "js-tokens": "^4.0.0"
           }
@@ -66,6 +66,12 @@
             "ms": "^2.1.1"
           }
         },
+        "lodash": {
+          "version": "4.17.19",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+          "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+          "dev": true
+        },
         "ms": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -81,14 +87,13 @@
       }
     },
     "@babel/generator": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.1.tgz",
-      "integrity": "sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz",
+      "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1",
+        "@babel/types": "^7.10.5",
         "jsesc": "^2.5.1",
-        "lodash": "^4.17.13",
         "source-map": "^0.5.0"
       },
       "dependencies": {
@@ -101,119 +106,127 @@
       }
     },
     "@babel/helper-function-name": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz",
-      "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+      "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
       "dev": true,
       "requires": {
-        "@babel/helper-get-function-arity": "^7.10.1",
-        "@babel/template": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/helper-get-function-arity": "^7.10.4",
+        "@babel/template": "^7.10.4",
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-get-function-arity": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
-      "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
+      "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-member-expression-to-functions": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
-      "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz",
+      "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.5"
       }
     },
     "@babel/helper-module-imports": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz",
-      "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz",
+      "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-module-transforms": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz",
-      "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz",
+      "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==",
       "dev": true,
       "requires": {
-        "@babel/helper-module-imports": "^7.10.1",
-        "@babel/helper-replace-supers": "^7.10.1",
-        "@babel/helper-simple-access": "^7.10.1",
-        "@babel/helper-split-export-declaration": "^7.10.1",
-        "@babel/template": "^7.10.1",
-        "@babel/types": "^7.10.1",
-        "lodash": "^4.17.13"
+        "@babel/helper-module-imports": "^7.10.4",
+        "@babel/helper-replace-supers": "^7.10.4",
+        "@babel/helper-simple-access": "^7.10.4",
+        "@babel/helper-split-export-declaration": "^7.10.4",
+        "@babel/template": "^7.10.4",
+        "@babel/types": "^7.10.5",
+        "lodash": "^4.17.19"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.19",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+          "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+          "dev": true
+        }
       }
     },
     "@babel/helper-optimise-call-expression": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
-      "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz",
+      "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-plugin-utils": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
-      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+      "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
       "dev": true
     },
     "@babel/helper-replace-supers": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz",
-      "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz",
+      "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==",
       "dev": true,
       "requires": {
-        "@babel/helper-member-expression-to-functions": "^7.10.1",
-        "@babel/helper-optimise-call-expression": "^7.10.1",
-        "@babel/traverse": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/helper-member-expression-to-functions": "^7.10.4",
+        "@babel/helper-optimise-call-expression": "^7.10.4",
+        "@babel/traverse": "^7.10.4",
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-simple-access": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz",
-      "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz",
+      "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==",
       "dev": true,
       "requires": {
-        "@babel/template": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/template": "^7.10.4",
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-split-export-declaration": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz",
-      "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz",
+      "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.10.1"
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/helper-validator-identifier": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
-      "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+      "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
       "dev": true
     },
     "@babel/helpers": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz",
-      "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz",
+      "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==",
       "dev": true,
       "requires": {
-        "@babel/template": "^7.10.1",
-        "@babel/traverse": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/template": "^7.10.4",
+        "@babel/traverse": "^7.10.4",
+        "@babel/types": "^7.10.4"
       }
     },
     "@babel/highlight": {
@@ -236,9 +249,9 @@
       }
     },
     "@babel/parser": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz",
-      "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz",
+      "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==",
       "dev": true
     },
     "@babel/plugin-syntax-async-generators": {
@@ -260,12 +273,21 @@
       }
     },
     "@babel/plugin-syntax-class-properties": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz",
-      "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz",
+      "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.4"
+      }
+    },
+    "@babel/plugin-syntax-import-meta": {
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+      "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.10.4"
       }
     },
     "@babel/plugin-syntax-json-strings": {
@@ -278,12 +300,12 @@
       }
     },
     "@babel/plugin-syntax-logical-assignment-operators": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.1.tgz",
-      "integrity": "sha512-XyHIFa9kdrgJS91CUH+ccPVTnJShr8nLGc5bG2IhGXv5p1Rd+8BleGE5yzIg2Nc1QZAdHDa0Qp4m6066OL96Iw==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+      "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.4"
       }
     },
     "@babel/plugin-syntax-nullish-coalescing-operator": {
@@ -296,12 +318,12 @@
       }
     },
     "@babel/plugin-syntax-numeric-separator": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz",
-      "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+      "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
       "dev": true,
       "requires": {
-        "@babel/helper-plugin-utils": "^7.10.1"
+        "@babel/helper-plugin-utils": "^7.10.4"
       }
     },
     "@babel/plugin-syntax-object-rest-spread": {
@@ -332,32 +354,32 @@
       }
     },
     "@babel/template": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz",
-      "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==",
+      "version": "7.10.4",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
+      "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.10.1",
-        "@babel/parser": "^7.10.1",
-        "@babel/types": "^7.10.1"
+        "@babel/code-frame": "^7.10.4",
+        "@babel/parser": "^7.10.4",
+        "@babel/types": "^7.10.4"
       },
       "dependencies": {
         "@babel/code-frame": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
-          "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+          "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
           "dev": true,
           "requires": {
-            "@babel/highlight": "^7.10.1"
+            "@babel/highlight": "^7.10.4"
           }
         },
         "@babel/highlight": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
-          "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+          "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
           "dev": true,
           "requires": {
-            "@babel/helper-validator-identifier": "^7.10.1",
+            "@babel/helper-validator-identifier": "^7.10.4",
             "chalk": "^2.0.0",
             "js-tokens": "^4.0.0"
           }
@@ -365,38 +387,38 @@
       }
     },
     "@babel/traverse": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz",
-      "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz",
+      "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.10.1",
-        "@babel/generator": "^7.10.1",
-        "@babel/helper-function-name": "^7.10.1",
-        "@babel/helper-split-export-declaration": "^7.10.1",
-        "@babel/parser": "^7.10.1",
-        "@babel/types": "^7.10.1",
+        "@babel/code-frame": "^7.10.4",
+        "@babel/generator": "^7.10.5",
+        "@babel/helper-function-name": "^7.10.4",
+        "@babel/helper-split-export-declaration": "^7.10.4",
+        "@babel/parser": "^7.10.5",
+        "@babel/types": "^7.10.5",
         "debug": "^4.1.0",
         "globals": "^11.1.0",
-        "lodash": "^4.17.13"
+        "lodash": "^4.17.19"
       },
       "dependencies": {
         "@babel/code-frame": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
-          "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+          "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
           "dev": true,
           "requires": {
-            "@babel/highlight": "^7.10.1"
+            "@babel/highlight": "^7.10.4"
           }
         },
         "@babel/highlight": {
-          "version": "7.10.1",
-          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
-          "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
+          "version": "7.10.4",
+          "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+          "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
           "dev": true,
           "requires": {
-            "@babel/helper-validator-identifier": "^7.10.1",
+            "@babel/helper-validator-identifier": "^7.10.4",
             "chalk": "^2.0.0",
             "js-tokens": "^4.0.0"
           }
@@ -410,6 +432,12 @@
             "ms": "^2.1.1"
           }
         },
+        "lodash": {
+          "version": "4.17.19",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+          "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+          "dev": true
+        },
         "ms": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -419,14 +447,22 @@
       }
     },
     "@babel/types": {
-      "version": "7.10.1",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
-      "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
+      "version": "7.10.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz",
+      "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==",
       "dev": true,
       "requires": {
-        "@babel/helper-validator-identifier": "^7.10.1",
-        "lodash": "^4.17.13",
+        "@babel/helper-validator-identifier": "^7.10.4",
+        "lodash": "^4.17.19",
         "to-fast-properties": "^2.0.0"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.19",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+          "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+          "dev": true
+        }
       }
     },
     "@bcoe/v8-coverage": {
@@ -465,15 +501,15 @@
       "dev": true
     },
     "@jest/console": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.0.1.tgz",
-      "integrity": "sha512-9t1KUe/93coV1rBSxMmBAOIK3/HVpwxArCA1CxskKyRiv6o8J70V8C/V3OJminVCTa2M0hQI9AWRd5wxu2dAHw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz",
+      "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
-        "jest-message-util": "^26.0.1",
-        "jest-util": "^26.0.1",
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
+        "jest-message-util": "^25.5.0",
+        "jest-util": "^25.5.0",
         "slash": "^3.0.0"
       },
       "dependencies": {
@@ -488,24 +524,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -524,35 +551,36 @@
       }
     },
     "@jest/core": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.0.1.tgz",
-      "integrity": "sha512-Xq3eqYnxsG9SjDC+WLeIgf7/8KU6rddBxH+SCt18gEpOhAGYC/Mq+YbtlNcIdwjnnT+wDseXSbU0e5X84Y4jTQ==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz",
+      "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==",
       "dev": true,
       "requires": {
-        "@jest/console": "^26.0.1",
-        "@jest/reporters": "^26.0.1",
-        "@jest/test-result": "^26.0.1",
-        "@jest/transform": "^26.0.1",
-        "@jest/types": "^26.0.1",
+        "@jest/console": "^25.5.0",
+        "@jest/reporters": "^25.5.1",
+        "@jest/test-result": "^25.5.0",
+        "@jest/transform": "^25.5.1",
+        "@jest/types": "^25.5.0",
         "ansi-escapes": "^4.2.1",
-        "chalk": "^4.0.0",
+        "chalk": "^3.0.0",
         "exit": "^0.1.2",
         "graceful-fs": "^4.2.4",
-        "jest-changed-files": "^26.0.1",
-        "jest-config": "^26.0.1",
-        "jest-haste-map": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-regex-util": "^26.0.0",
-        "jest-resolve": "^26.0.1",
-        "jest-resolve-dependencies": "^26.0.1",
-        "jest-runner": "^26.0.1",
-        "jest-runtime": "^26.0.1",
-        "jest-snapshot": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jest-validate": "^26.0.1",
-        "jest-watcher": "^26.0.1",
+        "jest-changed-files": "^25.5.0",
+        "jest-config": "^25.5.4",
+        "jest-haste-map": "^25.5.1",
+        "jest-message-util": "^25.5.0",
+        "jest-regex-util": "^25.2.6",
+        "jest-resolve": "^25.5.1",
+        "jest-resolve-dependencies": "^25.5.4",
+        "jest-runner": "^25.5.4",
+        "jest-runtime": "^25.5.4",
+        "jest-snapshot": "^25.5.1",
+        "jest-util": "^25.5.0",
+        "jest-validate": "^25.5.0",
+        "jest-watcher": "^25.5.0",
         "micromatch": "^4.0.2",
         "p-each-series": "^2.1.0",
+        "realpath-native": "^2.0.0",
         "rimraf": "^3.0.0",
         "slash": "^3.0.0",
         "strip-ansi": "^6.0.0"
@@ -575,24 +603,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "glob": {
           "version": "7.1.6",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -649,52 +668,52 @@
       }
     },
     "@jest/environment": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.0.1.tgz",
-      "integrity": "sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz",
+      "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==",
       "dev": true,
       "requires": {
-        "@jest/fake-timers": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "jest-mock": "^26.0.1"
+        "@jest/fake-timers": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "jest-mock": "^25.5.0"
       }
     },
     "@jest/fake-timers": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.0.1.tgz",
-      "integrity": "sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz",
+      "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "@sinonjs/fake-timers": "^6.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-mock": "^26.0.1",
-        "jest-util": "^26.0.1"
+        "@jest/types": "^25.5.0",
+        "jest-message-util": "^25.5.0",
+        "jest-mock": "^25.5.0",
+        "jest-util": "^25.5.0",
+        "lolex": "^5.0.0"
       }
     },
     "@jest/globals": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.0.1.tgz",
-      "integrity": "sha512-iuucxOYB7BRCvT+TYBzUqUNuxFX1hqaR6G6IcGgEqkJ5x4htNKo1r7jk1ji9Zj8ZMiMw0oB5NaA7k5Tx6MVssA==",
+      "version": "25.5.2",
+      "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-25.5.2.tgz",
+      "integrity": "sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==",
       "dev": true,
       "requires": {
-        "@jest/environment": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "expect": "^26.0.1"
+        "@jest/environment": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "expect": "^25.5.0"
       }
     },
     "@jest/reporters": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.0.1.tgz",
-      "integrity": "sha512-NWWy9KwRtE1iyG/m7huiFVF9YsYv/e+mbflKRV84WDoJfBqUrNRyDbL/vFxQcYLl8IRqI4P3MgPn386x76Gf2g==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.5.1.tgz",
+      "integrity": "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==",
       "dev": true,
       "requires": {
         "@bcoe/v8-coverage": "^0.2.3",
-        "@jest/console": "^26.0.1",
-        "@jest/test-result": "^26.0.1",
-        "@jest/transform": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/console": "^25.5.0",
+        "@jest/test-result": "^25.5.0",
+        "@jest/transform": "^25.5.1",
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
         "collect-v8-coverage": "^1.0.0",
         "exit": "^0.1.2",
         "glob": "^7.1.2",
@@ -704,14 +723,14 @@
         "istanbul-lib-report": "^3.0.0",
         "istanbul-lib-source-maps": "^4.0.0",
         "istanbul-reports": "^3.0.2",
-        "jest-haste-map": "^26.0.1",
-        "jest-resolve": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jest-worker": "^26.0.0",
-        "node-notifier": "^7.0.0",
+        "jest-haste-map": "^25.5.1",
+        "jest-resolve": "^25.5.1",
+        "jest-util": "^25.5.0",
+        "jest-worker": "^25.5.0",
+        "node-notifier": "^6.0.0",
         "slash": "^3.0.0",
         "source-map": "^0.6.0",
-        "string-length": "^4.0.1",
+        "string-length": "^3.1.0",
         "terminal-link": "^2.0.0",
         "v8-to-istanbul": "^4.1.3"
       },
@@ -727,24 +746,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -769,9 +779,9 @@
       }
     },
     "@jest/source-map": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.0.0.tgz",
-      "integrity": "sha512-S2Z+Aj/7KOSU2TfW0dyzBze7xr95bkm5YXNUqqCek+HE0VbNNSNzrRwfIi5lf7wvzDTSS0/ib8XQ1krFNyYgbQ==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.5.0.tgz",
+      "integrity": "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==",
       "dev": true,
       "requires": {
         "callsites": "^3.0.0",
@@ -788,28 +798,28 @@
       }
     },
     "@jest/test-result": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.0.1.tgz",
-      "integrity": "sha512-oKwHvOI73ICSYRPe8WwyYPTtiuOAkLSbY8/MfWF3qDEd/sa8EDyZzin3BaXTqufir/O/Gzea4E8Zl14XU4Mlyg==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz",
+      "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==",
       "dev": true,
       "requires": {
-        "@jest/console": "^26.0.1",
-        "@jest/types": "^26.0.1",
+        "@jest/console": "^25.5.0",
+        "@jest/types": "^25.5.0",
         "@types/istanbul-lib-coverage": "^2.0.0",
         "collect-v8-coverage": "^1.0.0"
       }
     },
     "@jest/test-sequencer": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz",
-      "integrity": "sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz",
+      "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==",
       "dev": true,
       "requires": {
-        "@jest/test-result": "^26.0.1",
+        "@jest/test-result": "^25.5.0",
         "graceful-fs": "^4.2.4",
-        "jest-haste-map": "^26.0.1",
-        "jest-runner": "^26.0.1",
-        "jest-runtime": "^26.0.1"
+        "jest-haste-map": "^25.5.1",
+        "jest-runner": "^25.5.4",
+        "jest-runtime": "^25.5.4"
       },
       "dependencies": {
         "graceful-fs": {
@@ -821,23 +831,24 @@
       }
     },
     "@jest/transform": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.0.1.tgz",
-      "integrity": "sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz",
+      "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.1.0",
-        "@jest/types": "^26.0.1",
+        "@jest/types": "^25.5.0",
         "babel-plugin-istanbul": "^6.0.0",
-        "chalk": "^4.0.0",
+        "chalk": "^3.0.0",
         "convert-source-map": "^1.4.0",
         "fast-json-stable-stringify": "^2.0.0",
         "graceful-fs": "^4.2.4",
-        "jest-haste-map": "^26.0.1",
-        "jest-regex-util": "^26.0.0",
-        "jest-util": "^26.0.1",
+        "jest-haste-map": "^25.5.1",
+        "jest-regex-util": "^25.2.6",
+        "jest-util": "^25.5.0",
         "micromatch": "^4.0.2",
         "pirates": "^4.0.1",
+        "realpath-native": "^2.0.0",
         "slash": "^3.0.0",
         "source-map": "^0.6.1",
         "write-file-atomic": "^3.0.0"
@@ -854,24 +865,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -896,15 +898,15 @@
       }
     },
     "@jest/types": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz",
-      "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz",
+      "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==",
       "dev": true,
       "requires": {
         "@types/istanbul-lib-coverage": "^2.0.0",
         "@types/istanbul-reports": "^1.1.1",
         "@types/yargs": "^15.0.0",
-        "chalk": "^4.0.0"
+        "chalk": "^3.0.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -918,24 +920,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1007,33 +1000,107 @@
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
     },
+    "@rollup/plugin-commonjs": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz",
+      "integrity": "sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw==",
+      "dev": true,
+      "requires": {
+        "@rollup/pluginutils": "^3.0.8",
+        "commondir": "^1.0.1",
+        "estree-walker": "^1.0.1",
+        "glob": "^7.1.2",
+        "is-reference": "^1.1.2",
+        "magic-string": "^0.25.2",
+        "resolve": "^1.11.0"
+      },
+      "dependencies": {
+        "estree-walker": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+          "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+          "dev": true
+        }
+      }
+    },
+    "@rollup/plugin-node-resolve": {
+      "version": "8.4.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz",
+      "integrity": "sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ==",
+      "dev": true,
+      "requires": {
+        "@rollup/pluginutils": "^3.1.0",
+        "@types/resolve": "1.17.1",
+        "builtin-modules": "^3.1.0",
+        "deep-freeze": "^0.0.1",
+        "deepmerge": "^4.2.2",
+        "is-module": "^1.0.0",
+        "resolve": "^1.17.0"
+      },
+      "dependencies": {
+        "@types/resolve": {
+          "version": "1.17.1",
+          "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+          "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+          "dev": true,
+          "requires": {
+            "@types/node": "*"
+          }
+        },
+        "resolve": {
+          "version": "1.17.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+          "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+          "dev": true,
+          "requires": {
+            "path-parse": "^1.0.6"
+          }
+        }
+      }
+    },
+    "@rollup/pluginutils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+      "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "0.0.39",
+        "estree-walker": "^1.0.1",
+        "picomatch": "^2.2.2"
+      },
+      "dependencies": {
+        "@types/estree": {
+          "version": "0.0.39",
+          "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+          "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+          "dev": true
+        },
+        "estree-walker": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+          "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+          "dev": true
+        }
+      }
+    },
     "@sinonjs/commons": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz",
-      "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==",
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
+      "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==",
       "dev": true,
       "requires": {
         "type-detect": "4.0.8"
       }
     },
-    "@sinonjs/fake-timers": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
-      "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
-      "dev": true,
-      "requires": {
-        "@sinonjs/commons": "^1.7.0"
-      }
-    },
     "@tsundoku/micromodal_types": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/@tsundoku/micromodal_types/-/micromodal_types-0.0.1.tgz",
       "integrity": "sha512-9k95tyHczZp/Uwu7SysnekpA2/o/y5gb/jMwqoLuTlJqwIVwnxfpsfmxc/bMfHnct7ESSqmRUJ1qYnUPD9Z7og=="
     },
     "@types/babel__core": {
-      "version": "7.1.7",
-      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz",
-      "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==",
+      "version": "7.1.9",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz",
+      "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==",
       "dev": true,
       "requires": {
         "@babel/parser": "^7.1.0",
@@ -1063,9 +1130,9 @@
       }
     },
     "@types/babel__traverse": {
-      "version": "7.0.11",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz",
-      "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==",
+      "version": "7.0.13",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz",
+      "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==",
       "dev": true,
       "requires": {
         "@babel/types": "^7.3.0"
@@ -1120,10 +1187,15 @@
         "@types/node": "*"
       }
     },
+    "@types/gtag.js": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.3.tgz",
+      "integrity": "sha512-iRF/4Q3G1t0OTNMjK52tpGSQPgEsYzWQL1IdVXt9BywK6MUK3ypB7LaoEQa8sW9gPXENoU1xAyCxqJhMkB2rCA=="
+    },
     "@types/istanbul-lib-coverage": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz",
-      "integrity": "sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
+      "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
       "dev": true
     },
     "@types/istanbul-lib-report": {
@@ -1178,9 +1250,9 @@
       "integrity": "sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg=="
     },
     "@types/prettier": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz",
-      "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==",
+      "version": "1.19.1",
+      "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz",
+      "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==",
       "dev": true
     },
     "@types/puppeteer": {
@@ -1192,15 +1264,6 @@
         "@types/node": "*"
       }
     },
-    "@types/resolve": {
-      "version": "0.0.8",
-      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
-      "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
-      "dev": true,
-      "requires": {
-        "@types/node": "*"
-      }
-    },
     "@types/stack-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -1241,7 +1304,8 @@
     "abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
     },
     "acorn": {
       "version": "7.2.0",
@@ -1250,19 +1314,27 @@
       "dev": true
     },
     "acorn-globals": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
-      "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz",
+      "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
       "dev": true,
       "requires": {
-        "acorn": "^7.1.1",
-        "acorn-walk": "^7.1.1"
+        "acorn": "^6.0.1",
+        "acorn-walk": "^6.0.1"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "6.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+          "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+          "dev": true
+        }
       }
     },
     "acorn-walk": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
-      "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==",
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
+      "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
       "dev": true
     },
     "agent-base": {
@@ -1312,7 +1384,8 @@
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+      "dev": true
     },
     "ansi-styles": {
       "version": "3.2.1",
@@ -1353,12 +1426,14 @@
     "aproba": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
-      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
     },
     "are-we-there-yet": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
       "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "dev": true,
       "requires": {
         "delegates": "^1.0.0",
         "readable-stream": "^2.0.6"
@@ -1391,6 +1466,12 @@
       "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
       "dev": true
     },
+    "array-equal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+      "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
+      "dev": true
+    },
     "array-filter": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz",
@@ -1429,6 +1510,12 @@
       "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
       "dev": true
     },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
     "async-foreach": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
@@ -1474,17 +1561,17 @@
       "dev": true
     },
     "babel-jest": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.1.tgz",
-      "integrity": "sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz",
+      "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==",
       "dev": true,
       "requires": {
-        "@jest/transform": "^26.0.1",
-        "@jest/types": "^26.0.1",
+        "@jest/transform": "^25.5.1",
+        "@jest/types": "^25.5.0",
         "@types/babel__core": "^7.1.7",
         "babel-plugin-istanbul": "^6.0.0",
-        "babel-preset-jest": "^26.0.0",
-        "chalk": "^4.0.0",
+        "babel-preset-jest": "^25.5.0",
+        "chalk": "^3.0.0",
         "graceful-fs": "^4.2.4",
         "slash": "^3.0.0"
       },
@@ -1500,24 +1587,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -1555,9 +1633,9 @@
       }
     },
     "babel-plugin-jest-hoist": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz",
-      "integrity": "sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz",
+      "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==",
       "dev": true,
       "requires": {
         "@babel/template": "^7.3.3",
@@ -1566,14 +1644,15 @@
       }
     },
     "babel-preset-current-node-syntax": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz",
-      "integrity": "sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==",
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz",
+      "integrity": "sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==",
       "dev": true,
       "requires": {
         "@babel/plugin-syntax-async-generators": "^7.8.4",
         "@babel/plugin-syntax-bigint": "^7.8.3",
         "@babel/plugin-syntax-class-properties": "^7.8.3",
+        "@babel/plugin-syntax-import-meta": "^7.8.3",
         "@babel/plugin-syntax-json-strings": "^7.8.3",
         "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3",
         "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
@@ -1584,19 +1663,20 @@
       }
     },
     "babel-preset-jest": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz",
-      "integrity": "sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz",
+      "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==",
       "dev": true,
       "requires": {
-        "babel-plugin-jest-hoist": "^26.0.0",
+        "babel-plugin-jest-hoist": "^25.5.0",
         "babel-preset-current-node-syntax": "^0.1.2"
       }
     },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
     },
     "base": {
       "version": "0.11.2",
@@ -1675,6 +1755,7 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -1695,6 +1776,23 @@
       "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
       "dev": true
     },
+    "browser-resolve": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
+      "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
+      "dev": true,
+      "requires": {
+        "resolve": "1.1.7"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+          "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+          "dev": true
+        }
+      }
+    },
     "bser": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -1781,6 +1879,7 @@
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
       "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
+      "dev": true,
       "requires": {
         "nan": "^2.14.0",
         "node-pre-gyp": "^0.11.0",
@@ -1813,16 +1912,11 @@
         "supports-color": "^5.3.0"
       }
     },
-    "char-regex": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
-      "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
-      "dev": true
-    },
     "chownr": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
-      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "dev": true
     },
     "ci-info": {
       "version": "2.0.0",
@@ -1913,7 +2007,8 @@
     "code-point-at": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
     },
     "collect-v8-coverage": {
       "version": "1.0.1",
@@ -1959,6 +2054,12 @@
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
       "dev": true
     },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+      "dev": true
+    },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -1968,7 +2069,8 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
     },
     "concat-stream": {
       "version": "1.6.2",
@@ -1985,7 +2087,8 @@
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "dev": true
     },
     "convert-source-map": {
       "version": "1.7.0",
@@ -2005,7 +2108,8 @@
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "dev": true
     },
     "cross-spawn": {
       "version": "6.0.5",
@@ -2065,14 +2169,14 @@
       }
     },
     "data-urls": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
-      "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
+      "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
       "dev": true,
       "requires": {
-        "abab": "^2.0.3",
-        "whatwg-mimetype": "^2.3.0",
-        "whatwg-url": "^8.0.0"
+        "abab": "^2.0.0",
+        "whatwg-mimetype": "^2.2.0",
+        "whatwg-url": "^7.0.0"
       }
     },
     "debug": {
@@ -2090,12 +2194,6 @@
       "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
       "dev": true
     },
-    "decimal.js": {
-      "version": "10.2.0",
-      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
-      "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==",
-      "dev": true
-    },
     "decode-uri-component": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -2106,6 +2204,7 @@
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
       "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+      "dev": true,
       "requires": {
         "mimic-response": "^2.0.0"
       }
@@ -2113,7 +2212,14 @@
     "deep-extend": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
-      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true
+    },
+    "deep-freeze": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz",
+      "integrity": "sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=",
+      "dev": true
     },
     "deep-is": {
       "version": "0.1.3",
@@ -2185,12 +2291,14 @@
     "delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+      "dev": true
     },
     "detect-libc": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
-      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
+      "dev": true
     },
     "detect-newline": {
       "version": "3.1.0",
@@ -2210,9 +2318,9 @@
       "dev": true
     },
     "diff-sequences": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz",
-      "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==",
+      "version": "25.2.6",
+      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz",
+      "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==",
       "dev": true
     },
     "dingusjs": {
@@ -2222,20 +2330,12 @@
       "dev": true
     },
     "domexception": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
-      "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+      "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
       "dev": true,
       "requires": {
-        "webidl-conversions": "^5.0.0"
-      },
-      "dependencies": {
-        "webidl-conversions": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
-          "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==",
-          "dev": true
-        }
+        "webidl-conversions": "^4.0.2"
       }
     },
     "ecc-jsbn": {
@@ -2330,9 +2430,9 @@
       "dev": true
     },
     "escodegen": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
-      "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
+      "version": "1.14.3",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
+      "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
       "dev": true,
       "requires": {
         "esprima": "^4.0.1",
@@ -2354,12 +2454,6 @@
       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
       "dev": true
     },
-    "estree-walker": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz",
-      "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==",
-      "dev": true
-    },
     "esutils": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
@@ -2434,17 +2528,17 @@
       }
     },
     "expect": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/expect/-/expect-26.0.1.tgz",
-      "integrity": "sha512-QcCy4nygHeqmbw564YxNbHTJlXh47dVID2BUP52cZFpLU9zHViMFK6h07cC1wf7GYCTIigTdAXhVua8Yl1FkKg==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz",
+      "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
+        "@jest/types": "^25.5.0",
         "ansi-styles": "^4.0.0",
-        "jest-get-type": "^26.0.0",
-        "jest-matcher-utils": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-regex-util": "^26.0.0"
+        "jest-get-type": "^25.2.6",
+        "jest-matcher-utils": "^25.5.0",
+        "jest-message-util": "^25.5.0",
+        "jest-regex-util": "^25.2.6"
       },
       "dependencies": {
         "ansi-styles": {
@@ -2456,15 +2550,6 @@
             "@types/color-name": "^1.1.1",
             "color-convert": "^2.0.1"
           }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
         }
       }
     },
@@ -2682,6 +2767,7 @@
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
       "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
+      "dev": true,
       "requires": {
         "minipass": "^2.6.0"
       }
@@ -2689,7 +2775,8 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
     },
     "fsevents": {
       "version": "2.1.3",
@@ -2719,6 +2806,7 @@
       "version": "2.7.4",
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
       "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "dev": true,
       "requires": {
         "aproba": "^1.0.3",
         "console-control-strings": "^1.0.0",
@@ -2734,6 +2822,7 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -2742,6 +2831,7 @@
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -2752,6 +2842,7 @@
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -2819,6 +2910,7 @@
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
       "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -2905,7 +2997,8 @@
     "has-unicode": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+      "dev": true
     },
     "has-value": {
       "version": "1.0.0",
@@ -2966,12 +3059,12 @@
       "dev": true
     },
     "html-encoding-sniffer": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
-      "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
+      "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
       "dev": true,
       "requires": {
-        "whatwg-encoding": "^1.0.5"
+        "whatwg-encoding": "^1.0.1"
       }
     },
     "html-escaper": {
@@ -3028,6 +3121,7 @@
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
       "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
       "requires": {
         "safer-buffer": ">= 2.1.2 < 3"
       }
@@ -3036,6 +3130,7 @@
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
       "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+      "dev": true,
       "requires": {
         "minimatch": "^3.0.4"
       }
@@ -3080,6 +3175,7 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -3093,7 +3189,8 @@
     "ini": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
-      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
     },
     "ip-regex": {
       "version": "2.1.0",
@@ -3221,7 +3318,8 @@
     "is-fullwidth-code-point": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
     },
     "is-generator-fn": {
       "version": "2.1.0",
@@ -3255,11 +3353,14 @@
         "isobject": "^3.0.1"
       }
     },
-    "is-potential-custom-element-name": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz",
-      "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=",
-      "dev": true
+    "is-reference": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+      "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "*"
+      }
     },
     "is-regex": {
       "version": "1.0.5",
@@ -3325,7 +3426,8 @@
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
     },
     "isexe": {
       "version": "2.0.0",
@@ -3438,14 +3540,14 @@
       }
     },
     "jest": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest/-/jest-26.0.1.tgz",
-      "integrity": "sha512-29Q54kn5Bm7ZGKIuH2JRmnKl85YRigp0o0asTc6Sb6l2ch1DCXIeZTLLFy9ultJvhkTqbswF5DEx4+RlkmCxWg==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz",
+      "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==",
       "dev": true,
       "requires": {
-        "@jest/core": "^26.0.1",
+        "@jest/core": "^25.5.4",
         "import-local": "^3.0.2",
-        "jest-cli": "^26.0.1"
+        "jest-cli": "^25.5.4"
       },
       "dependencies": {
         "ansi-styles": {
@@ -3459,24 +3561,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -3490,23 +3583,24 @@
           "dev": true
         },
         "jest-cli": {
-          "version": "26.0.1",
-          "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.0.1.tgz",
-          "integrity": "sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w==",
+          "version": "25.5.4",
+          "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz",
+          "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==",
           "dev": true,
           "requires": {
-            "@jest/core": "^26.0.1",
-            "@jest/test-result": "^26.0.1",
-            "@jest/types": "^26.0.1",
-            "chalk": "^4.0.0",
+            "@jest/core": "^25.5.4",
+            "@jest/test-result": "^25.5.0",
+            "@jest/types": "^25.5.0",
+            "chalk": "^3.0.0",
             "exit": "^0.1.2",
             "graceful-fs": "^4.2.4",
             "import-local": "^3.0.2",
             "is-ci": "^2.0.0",
-            "jest-config": "^26.0.1",
-            "jest-util": "^26.0.1",
-            "jest-validate": "^26.0.1",
+            "jest-config": "^25.5.4",
+            "jest-util": "^25.5.0",
+            "jest-validate": "^25.5.0",
             "prompts": "^2.0.1",
+            "realpath-native": "^2.0.0",
             "yargs": "^15.3.1"
           }
         },
@@ -3522,13 +3616,13 @@
       }
     },
     "jest-changed-files": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.0.1.tgz",
-      "integrity": "sha512-q8LP9Sint17HaE2LjxQXL+oYWW/WeeXMPE2+Op9X3mY8IEGFVc14xRxFjUuXUbcPAlDLhtWdIEt59GdQbn76Hw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.5.0.tgz",
+      "integrity": "sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "execa": "^4.0.0",
+        "@jest/types": "^25.5.0",
+        "execa": "^3.2.0",
         "throat": "^5.0.0"
       },
       "dependencies": {
@@ -3544,9 +3638,9 @@
           }
         },
         "execa": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz",
-          "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==",
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz",
+          "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==",
           "dev": true,
           "requires": {
             "cross-spawn": "^7.0.0",
@@ -3556,6 +3650,7 @@
             "merge-stream": "^2.0.0",
             "npm-run-path": "^4.0.0",
             "onetime": "^5.1.0",
+            "p-finally": "^2.0.0",
             "signal-exit": "^3.0.2",
             "strip-final-newline": "^2.0.0"
           }
@@ -3584,6 +3679,12 @@
             "path-key": "^3.0.0"
           }
         },
+        "p-finally": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
+          "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==",
+          "dev": true
+        },
         "path-key": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -3617,29 +3718,30 @@
       }
     },
     "jest-config": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.0.1.tgz",
-      "integrity": "sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz",
+      "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.1.0",
-        "@jest/test-sequencer": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "babel-jest": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/test-sequencer": "^25.5.4",
+        "@jest/types": "^25.5.0",
+        "babel-jest": "^25.5.1",
+        "chalk": "^3.0.0",
         "deepmerge": "^4.2.2",
         "glob": "^7.1.1",
         "graceful-fs": "^4.2.4",
-        "jest-environment-jsdom": "^26.0.1",
-        "jest-environment-node": "^26.0.1",
-        "jest-get-type": "^26.0.0",
-        "jest-jasmine2": "^26.0.1",
-        "jest-regex-util": "^26.0.0",
-        "jest-resolve": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jest-validate": "^26.0.1",
+        "jest-environment-jsdom": "^25.5.0",
+        "jest-environment-node": "^25.5.0",
+        "jest-get-type": "^25.2.6",
+        "jest-jasmine2": "^25.5.4",
+        "jest-regex-util": "^25.2.6",
+        "jest-resolve": "^25.5.1",
+        "jest-util": "^25.5.0",
+        "jest-validate": "^25.5.0",
         "micromatch": "^4.0.2",
-        "pretty-format": "^26.0.1"
+        "pretty-format": "^25.5.0",
+        "realpath-native": "^2.0.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -3653,24 +3755,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -3695,15 +3788,15 @@
       }
     },
     "jest-diff": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.0.1.tgz",
-      "integrity": "sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz",
+      "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==",
       "dev": true,
       "requires": {
-        "chalk": "^4.0.0",
-        "diff-sequences": "^26.0.0",
-        "jest-get-type": "^26.0.0",
-        "pretty-format": "^26.0.1"
+        "chalk": "^3.0.0",
+        "diff-sequences": "^25.2.6",
+        "jest-get-type": "^25.2.6",
+        "pretty-format": "^25.5.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -3717,24 +3810,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3753,25 +3837,25 @@
       }
     },
     "jest-docblock": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz",
-      "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==",
+      "version": "25.3.0",
+      "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.3.0.tgz",
+      "integrity": "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==",
       "dev": true,
       "requires": {
         "detect-newline": "^3.0.0"
       }
     },
     "jest-each": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.0.1.tgz",
-      "integrity": "sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz",
+      "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
-        "jest-get-type": "^26.0.0",
-        "jest-util": "^26.0.1",
-        "pretty-format": "^26.0.1"
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
+        "jest-get-type": "^25.2.6",
+        "jest-util": "^25.5.0",
+        "pretty-format": "^25.5.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -3785,24 +3869,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3821,53 +3896,62 @@
       }
     },
     "jest-environment-jsdom": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz",
-      "integrity": "sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz",
+      "integrity": "sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==",
       "dev": true,
       "requires": {
-        "@jest/environment": "^26.0.1",
-        "@jest/fake-timers": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "jest-mock": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jsdom": "^16.2.2"
+        "@jest/environment": "^25.5.0",
+        "@jest/fake-timers": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "jest-mock": "^25.5.0",
+        "jest-util": "^25.5.0",
+        "jsdom": "^15.2.1"
       }
     },
     "jest-environment-node": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.0.1.tgz",
-      "integrity": "sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.5.0.tgz",
+      "integrity": "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==",
       "dev": true,
       "requires": {
-        "@jest/environment": "^26.0.1",
-        "@jest/fake-timers": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "jest-mock": "^26.0.1",
-        "jest-util": "^26.0.1"
+        "@jest/environment": "^25.5.0",
+        "@jest/fake-timers": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "jest-mock": "^25.5.0",
+        "jest-util": "^25.5.0",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
       }
     },
     "jest-get-type": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz",
-      "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==",
+      "version": "25.2.6",
+      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz",
+      "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==",
       "dev": true
     },
     "jest-haste-map": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.0.1.tgz",
-      "integrity": "sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz",
+      "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
+        "@jest/types": "^25.5.0",
         "@types/graceful-fs": "^4.1.2",
         "anymatch": "^3.0.3",
         "fb-watchman": "^2.0.0",
         "fsevents": "^2.1.2",
         "graceful-fs": "^4.2.4",
-        "jest-serializer": "^26.0.0",
-        "jest-util": "^26.0.1",
-        "jest-worker": "^26.0.0",
+        "jest-serializer": "^25.5.0",
+        "jest-util": "^25.5.0",
+        "jest-worker": "^25.5.0",
         "micromatch": "^4.0.2",
         "sane": "^4.0.3",
         "walker": "^1.0.7",
@@ -3892,27 +3976,27 @@
       }
     },
     "jest-jasmine2": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz",
-      "integrity": "sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz",
+      "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==",
       "dev": true,
       "requires": {
         "@babel/traverse": "^7.1.0",
-        "@jest/environment": "^26.0.1",
-        "@jest/source-map": "^26.0.0",
-        "@jest/test-result": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/environment": "^25.5.0",
+        "@jest/source-map": "^25.5.0",
+        "@jest/test-result": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
         "co": "^4.6.0",
-        "expect": "^26.0.1",
+        "expect": "^25.5.0",
         "is-generator-fn": "^2.0.0",
-        "jest-each": "^26.0.1",
-        "jest-matcher-utils": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-runtime": "^26.0.1",
-        "jest-snapshot": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "pretty-format": "^26.0.1",
+        "jest-each": "^25.5.0",
+        "jest-matcher-utils": "^25.5.0",
+        "jest-message-util": "^25.5.0",
+        "jest-runtime": "^25.5.4",
+        "jest-snapshot": "^25.5.1",
+        "jest-util": "^25.5.0",
+        "pretty-format": "^25.5.0",
         "throat": "^5.0.0"
       },
       "dependencies": {
@@ -3927,24 +4011,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3963,25 +4038,25 @@
       }
     },
     "jest-leak-detector": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.0.1.tgz",
-      "integrity": "sha512-93FR8tJhaYIWrWsbmVN1pQ9ZNlbgRpfvrnw5LmgLRX0ckOJ8ut/I35CL7awi2ecq6Ca4lL59bEK9hr7nqoHWPA==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz",
+      "integrity": "sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==",
       "dev": true,
       "requires": {
-        "jest-get-type": "^26.0.0",
-        "pretty-format": "^26.0.1"
+        "jest-get-type": "^25.2.6",
+        "pretty-format": "^25.5.0"
       }
     },
     "jest-matcher-utils": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz",
-      "integrity": "sha512-PUMlsLth0Azen8Q2WFTwnSkGh2JZ8FYuwijC8NR47vXKpsrKmA1wWvgcj1CquuVfcYiDEdj985u5Wmg7COEARw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz",
+      "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==",
       "dev": true,
       "requires": {
-        "chalk": "^4.0.0",
-        "jest-diff": "^26.0.1",
-        "jest-get-type": "^26.0.0",
-        "pretty-format": "^26.0.1"
+        "chalk": "^3.0.0",
+        "jest-diff": "^25.5.0",
+        "jest-get-type": "^25.2.6",
+        "pretty-format": "^25.5.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -3995,24 +4070,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4031,19 +4097,19 @@
       }
     },
     "jest-message-util": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.0.1.tgz",
-      "integrity": "sha512-CbK8uQREZ8umUfo8+zgIfEt+W7HAHjQCoRaNs4WxKGhAYBGwEyvxuK81FXa7VeB9pwDEXeeKOB2qcsNVCAvB7Q==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz",
+      "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==",
       "dev": true,
       "requires": {
         "@babel/code-frame": "^7.0.0",
-        "@jest/types": "^26.0.1",
+        "@jest/types": "^25.5.0",
         "@types/stack-utils": "^1.0.1",
-        "chalk": "^4.0.0",
+        "chalk": "^3.0.0",
         "graceful-fs": "^4.2.4",
         "micromatch": "^4.0.2",
         "slash": "^3.0.0",
-        "stack-utils": "^2.0.2"
+        "stack-utils": "^1.0.1"
       },
       "dependencies": {
         "ansi-styles": {
@@ -4057,24 +4123,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -4099,38 +4156,39 @@
       }
     },
     "jest-mock": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.0.1.tgz",
-      "integrity": "sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz",
+      "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1"
+        "@jest/types": "^25.5.0"
       }
     },
     "jest-pnp-resolver": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz",
-      "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
+      "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
       "dev": true
     },
     "jest-regex-util": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
-      "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==",
+      "version": "25.2.6",
+      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz",
+      "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==",
       "dev": true
     },
     "jest-resolve": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.0.1.tgz",
-      "integrity": "sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.5.1.tgz",
+      "integrity": "sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/types": "^25.5.0",
+        "browser-resolve": "^1.11.3",
+        "chalk": "^3.0.0",
         "graceful-fs": "^4.2.4",
         "jest-pnp-resolver": "^1.2.1",
-        "jest-util": "^26.0.1",
         "read-pkg-up": "^7.0.1",
+        "realpath-native": "^2.0.0",
         "resolve": "^1.17.0",
         "slash": "^3.0.0"
       },
@@ -4146,24 +4204,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -4177,9 +4226,9 @@
           "dev": true
         },
         "parse-json": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
-          "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz",
+          "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==",
           "dev": true,
           "requires": {
             "@babel/code-frame": "^7.0.0",
@@ -4240,39 +4289,39 @@
       }
     },
     "jest-resolve-dependencies": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.0.1.tgz",
-      "integrity": "sha512-9d5/RS/ft0vB/qy7jct/qAhzJsr6fRQJyGAFigK3XD4hf9kIbEH5gks4t4Z7kyMRhowU6HWm/o8ILqhaHdSqLw==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz",
+      "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "jest-regex-util": "^26.0.0",
-        "jest-snapshot": "^26.0.1"
+        "@jest/types": "^25.5.0",
+        "jest-regex-util": "^25.2.6",
+        "jest-snapshot": "^25.5.1"
       }
     },
     "jest-runner": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.0.1.tgz",
-      "integrity": "sha512-CApm0g81b49Znm4cZekYQK67zY7kkB4umOlI2Dx5CwKAzdgw75EN+ozBHRvxBzwo1ZLYZ07TFxkaPm+1t4d8jA==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz",
+      "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==",
       "dev": true,
       "requires": {
-        "@jest/console": "^26.0.1",
-        "@jest/environment": "^26.0.1",
-        "@jest/test-result": "^26.0.1",
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/console": "^25.5.0",
+        "@jest/environment": "^25.5.0",
+        "@jest/test-result": "^25.5.0",
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
         "exit": "^0.1.2",
         "graceful-fs": "^4.2.4",
-        "jest-config": "^26.0.1",
-        "jest-docblock": "^26.0.0",
-        "jest-haste-map": "^26.0.1",
-        "jest-jasmine2": "^26.0.1",
-        "jest-leak-detector": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-resolve": "^26.0.1",
-        "jest-runtime": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jest-worker": "^26.0.0",
+        "jest-config": "^25.5.4",
+        "jest-docblock": "^25.3.0",
+        "jest-haste-map": "^25.5.1",
+        "jest-jasmine2": "^25.5.4",
+        "jest-leak-detector": "^25.5.0",
+        "jest-message-util": "^25.5.0",
+        "jest-resolve": "^25.5.1",
+        "jest-runtime": "^25.5.4",
+        "jest-util": "^25.5.0",
+        "jest-worker": "^25.5.0",
         "source-map-support": "^0.5.6",
         "throat": "^5.0.0"
       },
@@ -4288,24 +4337,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -4330,34 +4370,34 @@
       }
     },
     "jest-runtime": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.0.1.tgz",
-      "integrity": "sha512-Ci2QhYFmANg5qaXWf78T2Pfo6GtmIBn2rRaLnklRyEucmPccmCKvS9JPljcmtVamsdMmkyNkVFb9pBTD6si9Lw==",
+      "version": "25.5.4",
+      "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz",
+      "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==",
       "dev": true,
       "requires": {
-        "@jest/console": "^26.0.1",
-        "@jest/environment": "^26.0.1",
-        "@jest/fake-timers": "^26.0.1",
-        "@jest/globals": "^26.0.1",
-        "@jest/source-map": "^26.0.0",
-        "@jest/test-result": "^26.0.1",
-        "@jest/transform": "^26.0.1",
-        "@jest/types": "^26.0.1",
+        "@jest/console": "^25.5.0",
+        "@jest/environment": "^25.5.0",
+        "@jest/globals": "^25.5.2",
+        "@jest/source-map": "^25.5.0",
+        "@jest/test-result": "^25.5.0",
+        "@jest/transform": "^25.5.1",
+        "@jest/types": "^25.5.0",
         "@types/yargs": "^15.0.0",
-        "chalk": "^4.0.0",
+        "chalk": "^3.0.0",
         "collect-v8-coverage": "^1.0.0",
         "exit": "^0.1.2",
         "glob": "^7.1.3",
         "graceful-fs": "^4.2.4",
-        "jest-config": "^26.0.1",
-        "jest-haste-map": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-mock": "^26.0.1",
-        "jest-regex-util": "^26.0.0",
-        "jest-resolve": "^26.0.1",
-        "jest-snapshot": "^26.0.1",
-        "jest-util": "^26.0.1",
-        "jest-validate": "^26.0.1",
+        "jest-config": "^25.5.4",
+        "jest-haste-map": "^25.5.1",
+        "jest-message-util": "^25.5.0",
+        "jest-mock": "^25.5.0",
+        "jest-regex-util": "^25.2.6",
+        "jest-resolve": "^25.5.1",
+        "jest-snapshot": "^25.5.1",
+        "jest-util": "^25.5.0",
+        "jest-validate": "^25.5.0",
+        "realpath-native": "^2.0.0",
         "slash": "^3.0.0",
         "strip-bom": "^4.0.0",
         "yargs": "^15.3.1"
@@ -4374,24 +4414,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "glob": {
           "version": "7.1.6",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -4436,9 +4467,9 @@
       }
     },
     "jest-serializer": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.0.0.tgz",
-      "integrity": "sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz",
+      "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.2.4"
@@ -4453,26 +4484,26 @@
       }
     },
     "jest-snapshot": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.0.1.tgz",
-      "integrity": "sha512-jxd+cF7+LL+a80qh6TAnTLUZHyQoWwEHSUFJjkw35u3Gx+BZUNuXhYvDqHXr62UQPnWo2P6fvQlLjsU93UKyxA==",
+      "version": "25.5.1",
+      "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.5.1.tgz",
+      "integrity": "sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0",
-        "@jest/types": "^26.0.1",
-        "@types/prettier": "^2.0.0",
-        "chalk": "^4.0.0",
-        "expect": "^26.0.1",
+        "@jest/types": "^25.5.0",
+        "@types/prettier": "^1.19.0",
+        "chalk": "^3.0.0",
+        "expect": "^25.5.0",
         "graceful-fs": "^4.2.4",
-        "jest-diff": "^26.0.1",
-        "jest-get-type": "^26.0.0",
-        "jest-matcher-utils": "^26.0.1",
-        "jest-message-util": "^26.0.1",
-        "jest-resolve": "^26.0.1",
+        "jest-diff": "^25.5.0",
+        "jest-get-type": "^25.2.6",
+        "jest-matcher-utils": "^25.5.0",
+        "jest-message-util": "^25.5.0",
+        "jest-resolve": "^25.5.1",
         "make-dir": "^3.0.0",
         "natural-compare": "^1.4.0",
-        "pretty-format": "^26.0.1",
-        "semver": "^7.3.2"
+        "pretty-format": "^25.5.0",
+        "semver": "^6.3.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -4486,24 +4517,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -4517,9 +4539,9 @@
           "dev": true
         },
         "semver": {
-          "version": "7.3.2",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
-          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
           "dev": true
         },
         "supports-color": {
@@ -4534,13 +4556,13 @@
       }
     },
     "jest-util": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.0.1.tgz",
-      "integrity": "sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz",
+      "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "chalk": "^4.0.0",
+        "@jest/types": "^25.5.0",
+        "chalk": "^3.0.0",
         "graceful-fs": "^4.2.4",
         "is-ci": "^2.0.0",
         "make-dir": "^3.0.0"
@@ -4557,24 +4579,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "graceful-fs": {
           "version": "4.2.4",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@@ -4599,17 +4612,17 @@
       }
     },
     "jest-validate": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.0.1.tgz",
-      "integrity": "sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz",
+      "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
-        "camelcase": "^6.0.0",
-        "chalk": "^4.0.0",
-        "jest-get-type": "^26.0.0",
+        "@jest/types": "^25.5.0",
+        "camelcase": "^5.3.1",
+        "chalk": "^3.0.0",
+        "jest-get-type": "^25.2.6",
         "leven": "^3.1.0",
-        "pretty-format": "^26.0.1"
+        "pretty-format": "^25.5.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -4622,31 +4635,16 @@
             "color-convert": "^2.0.1"
           }
         },
-        "camelcase": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
-          "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==",
-          "dev": true
-        },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4665,17 +4663,17 @@
       }
     },
     "jest-watcher": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.0.1.tgz",
-      "integrity": "sha512-pdZPydsS8475f89kGswaNsN3rhP6lnC3/QDCppP7bg1L9JQz7oU9Mb/5xPETk1RHDCWeqmVC47M4K5RR7ejxFw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.5.0.tgz",
+      "integrity": "sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==",
       "dev": true,
       "requires": {
-        "@jest/test-result": "^26.0.1",
-        "@jest/types": "^26.0.1",
+        "@jest/test-result": "^25.5.0",
+        "@jest/types": "^25.5.0",
         "ansi-escapes": "^4.2.1",
-        "chalk": "^4.0.0",
-        "jest-util": "^26.0.1",
-        "string-length": "^4.0.1"
+        "chalk": "^3.0.0",
+        "jest-util": "^25.5.0",
+        "string-length": "^3.1.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -4689,24 +4687,15 @@
           }
         },
         "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
           "dev": true,
           "requires": {
             "ansi-styles": "^4.1.0",
             "supports-color": "^7.1.0"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4725,9 +4714,9 @@
       }
     },
     "jest-worker": {
-      "version": "26.0.0",
-      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz",
-      "integrity": "sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
+      "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
       "dev": true,
       "requires": {
         "merge-stream": "^2.0.0",
@@ -4779,85 +4768,38 @@
       "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
       "dev": true
     },
-    "jsbn-rsa": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/jsbn-rsa/-/jsbn-rsa-1.0.3.tgz",
-      "integrity": "sha512-mQIfIFLCok6A8IPsFUpvOIlQVS9ctcbbnznWDfpH4IFtt3GHq+qG4ujlcvBiGFY0T8/PCqkvSNFABd5zWMTeVg=="
-    },
     "jsdom": {
-      "version": "16.2.2",
-      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz",
-      "integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==",
+      "version": "15.2.1",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz",
+      "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==",
       "dev": true,
       "requires": {
-        "abab": "^2.0.3",
-        "acorn": "^7.1.1",
-        "acorn-globals": "^6.0.0",
-        "cssom": "^0.4.4",
-        "cssstyle": "^2.2.0",
-        "data-urls": "^2.0.0",
-        "decimal.js": "^10.2.0",
-        "domexception": "^2.0.1",
-        "escodegen": "^1.14.1",
-        "html-encoding-sniffer": "^2.0.1",
-        "is-potential-custom-element-name": "^1.0.0",
+        "abab": "^2.0.0",
+        "acorn": "^7.1.0",
+        "acorn-globals": "^4.3.2",
+        "array-equal": "^1.0.0",
+        "cssom": "^0.4.1",
+        "cssstyle": "^2.0.0",
+        "data-urls": "^1.1.0",
+        "domexception": "^1.0.1",
+        "escodegen": "^1.11.1",
+        "html-encoding-sniffer": "^1.0.2",
         "nwsapi": "^2.2.0",
-        "parse5": "5.1.1",
-        "request": "^2.88.2",
-        "request-promise-native": "^1.0.8",
-        "saxes": "^5.0.0",
-        "symbol-tree": "^3.2.4",
+        "parse5": "5.1.0",
+        "pn": "^1.1.0",
+        "request": "^2.88.0",
+        "request-promise-native": "^1.0.7",
+        "saxes": "^3.1.9",
+        "symbol-tree": "^3.2.2",
         "tough-cookie": "^3.0.1",
-        "w3c-hr-time": "^1.0.2",
-        "w3c-xmlserializer": "^2.0.0",
-        "webidl-conversions": "^6.0.0",
+        "w3c-hr-time": "^1.0.1",
+        "w3c-xmlserializer": "^1.1.2",
+        "webidl-conversions": "^4.0.2",
         "whatwg-encoding": "^1.0.5",
         "whatwg-mimetype": "^2.3.0",
-        "whatwg-url": "^8.0.0",
-        "ws": "^7.2.3",
+        "whatwg-url": "^7.0.0",
+        "ws": "^7.0.0",
         "xml-name-validator": "^3.0.0"
-      },
-      "dependencies": {
-        "request": {
-          "version": "2.88.2",
-          "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
-          "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
-          "dev": true,
-          "requires": {
-            "aws-sign2": "~0.7.0",
-            "aws4": "^1.8.0",
-            "caseless": "~0.12.0",
-            "combined-stream": "~1.0.6",
-            "extend": "~3.0.2",
-            "forever-agent": "~0.6.1",
-            "form-data": "~2.3.2",
-            "har-validator": "~5.1.3",
-            "http-signature": "~1.2.0",
-            "is-typedarray": "~1.0.0",
-            "isstream": "~0.1.2",
-            "json-stringify-safe": "~5.0.1",
-            "mime-types": "~2.1.19",
-            "oauth-sign": "~0.9.0",
-            "performance-now": "^2.1.0",
-            "qs": "~6.5.2",
-            "safe-buffer": "^5.1.2",
-            "tough-cookie": "~2.5.0",
-            "tunnel-agent": "^0.6.0",
-            "uuid": "^3.3.2"
-          },
-          "dependencies": {
-            "tough-cookie": {
-              "version": "2.5.0",
-              "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
-              "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
-              "dev": true,
-              "requires": {
-                "psl": "^1.1.28",
-                "punycode": "^2.1.1"
-              }
-            }
-          }
-        }
       }
     },
     "jsesc": {
@@ -4968,9 +4910,9 @@
       }
     },
     "lodash": {
-      "version": "4.17.15",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
-      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+      "version": "4.17.19",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
       "dev": true
     },
     "lodash.sortby": {
@@ -4979,6 +4921,15 @@
       "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
       "dev": true
     },
+    "lolex": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz",
+      "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==",
+      "dev": true,
+      "requires": {
+        "@sinonjs/commons": "^1.7.0"
+      }
+    },
     "long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
@@ -5137,12 +5088,14 @@
     "mimic-response": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
-      "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
+      "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+      "dev": true
     },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
@@ -5150,12 +5103,14 @@
     "minimist": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
     },
     "minipass": {
       "version": "2.9.0",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
       "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
+      "dev": true,
       "requires": {
         "safe-buffer": "^5.1.2",
         "yallist": "^3.0.0"
@@ -5164,7 +5119,8 @@
         "yallist": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
         }
       }
     },
@@ -5172,6 +5128,7 @@
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
       "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
+      "dev": true,
       "requires": {
         "minipass": "^2.9.0"
       }
@@ -5206,6 +5163,7 @@
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
       "requires": {
         "minimist": "^1.2.5"
       }
@@ -5219,7 +5177,8 @@
     "nan": {
       "version": "2.14.1",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
-      "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
+      "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
+      "dev": true
     },
     "nanomatch": {
       "version": "1.2.13",
@@ -5250,6 +5209,7 @@
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
       "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
+      "dev": true,
       "requires": {
         "debug": "^3.2.6",
         "iconv-lite": "^0.4.4",
@@ -5260,6 +5220,7 @@
           "version": "3.2.6",
           "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
           "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
           "requires": {
             "ms": "^2.1.1"
           }
@@ -5267,7 +5228,8 @@
         "ms": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
         }
       }
     },
@@ -5323,43 +5285,25 @@
       "dev": true
     },
     "node-notifier": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-7.0.1.tgz",
-      "integrity": "sha512-VkzhierE7DBmQEElhTGJIoiZa1oqRijOtgOlsXg32KrJRXsPy0NXFBqWGW/wTswnJlDCs5viRYaqWguqzsKcmg==",
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
+      "integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==",
       "dev": true,
       "optional": true,
       "requires": {
         "growly": "^1.3.0",
         "is-wsl": "^2.1.1",
-        "semver": "^7.2.1",
+        "semver": "^6.3.0",
         "shellwords": "^0.1.1",
-        "uuid": "^7.0.3",
-        "which": "^2.0.2"
+        "which": "^1.3.1"
       },
       "dependencies": {
         "semver": {
-          "version": "7.3.2",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
-          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
           "dev": true,
           "optional": true
-        },
-        "uuid": {
-          "version": "7.0.3",
-          "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
-          "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
-          "dev": true,
-          "optional": true
-        },
-        "which": {
-          "version": "2.0.2",
-          "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-          "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "isexe": "^2.0.0"
-          }
         }
       }
     },
@@ -5367,6 +5311,7 @@
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
       "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
+      "dev": true,
       "requires": {
         "detect-libc": "^1.0.2",
         "mkdirp": "^0.5.1",
@@ -5384,6 +5329,7 @@
           "version": "4.0.3",
           "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
           "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
+          "dev": true,
           "requires": {
             "abbrev": "1",
             "osenv": "^0.1.4"
@@ -5393,6 +5339,7 @@
           "version": "4.4.13",
           "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
           "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
+          "dev": true,
           "requires": {
             "chownr": "^1.1.1",
             "fs-minipass": "^1.2.5",
@@ -5406,7 +5353,8 @@
         "yallist": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
         }
       }
     },
@@ -5540,6 +5488,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
       "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
+      "dev": true,
       "requires": {
         "npm-normalize-package-bin": "^1.0.1"
       }
@@ -5547,12 +5496,14 @@
     "npm-normalize-package-bin": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
-      "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
+      "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
+      "dev": true
     },
     "npm-packlist": {
       "version": "1.4.8",
       "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
       "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
+      "dev": true,
       "requires": {
         "ignore-walk": "^3.0.1",
         "npm-bundled": "^1.0.1",
@@ -5572,6 +5523,7 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
       "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "dev": true,
       "requires": {
         "are-we-there-yet": "~1.1.2",
         "console-control-strings": "~1.1.0",
@@ -5582,7 +5534,8 @@
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+      "dev": true
     },
     "nwsapi": {
       "version": "2.2.0",
@@ -5599,7 +5552,8 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+      "dev": true
     },
     "object-copy": {
       "version": "0.1.0",
@@ -5675,6 +5629,7 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
       "requires": {
         "wrappy": "1"
       }
@@ -5705,17 +5660,20 @@
     "os-homedir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+      "dev": true
     },
     "os-tmpdir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
     },
     "osenv": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
       "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "dev": true,
       "requires": {
         "os-homedir": "^1.0.0",
         "os-tmpdir": "^1.0.0"
@@ -5772,9 +5730,9 @@
       }
     },
     "parse5": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
-      "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+      "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
       "dev": true
     },
     "pascalcase": {
@@ -5792,7 +5750,8 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
     },
     "path-key": {
       "version": "2.0.1",
@@ -5874,6 +5833,12 @@
         "find-up": "^4.0.0"
       }
     },
+    "pn": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
+      "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
+      "dev": true
+    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -5887,12 +5852,12 @@
       "dev": true
     },
     "pretty-format": {
-      "version": "26.0.1",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz",
-      "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==",
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
+      "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
       "dev": true,
       "requires": {
-        "@jest/types": "^26.0.1",
+        "@jest/types": "^25.5.0",
         "ansi-regex": "^5.0.0",
         "ansi-styles": "^4.0.0",
         "react-is": "^16.12.0"
@@ -5913,22 +5878,14 @@
             "@types/color-name": "^1.1.1",
             "color-convert": "^2.0.1"
           }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
         }
       }
     },
     "process-nextick-args": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+      "dev": true
     },
     "progress": {
       "version": "2.0.3",
@@ -6059,6 +6016,7 @@
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
       "requires": {
         "deep-extend": "^0.6.0",
         "ini": "~1.3.0",
@@ -6118,6 +6076,7 @@
       "version": "2.3.6",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
       "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "dev": true,
       "requires": {
         "core-util-is": "~1.0.0",
         "inherits": "~2.0.3",
@@ -6128,6 +6087,12 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "realpath-native": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz",
+      "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==",
+      "dev": true
+    },
     "redent": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -6222,21 +6187,29 @@
       }
     },
     "request-promise-core": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
-      "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
+      "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
       "dev": true,
       "requires": {
-        "lodash": "^4.17.15"
+        "lodash": "^4.17.19"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.19",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+          "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+          "dev": true
+        }
       }
     },
     "request-promise-native": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz",
-      "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==",
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
+      "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
       "dev": true,
       "requires": {
-        "request-promise-core": "1.1.3",
+        "request-promise-core": "1.1.4",
         "stealthy-require": "^1.1.1",
         "tough-cookie": "^2.3.3"
       },
@@ -6313,60 +6286,18 @@
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
       "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+      "dev": true,
       "requires": {
         "glob": "^7.0.5"
       }
     },
     "rollup": {
-      "version": "1.32.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz",
-      "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==",
+      "version": "2.23.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz",
+      "integrity": "sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg==",
       "dev": true,
       "requires": {
-        "@types/estree": "*",
-        "@types/node": "*",
-        "acorn": "^7.1.0"
-      }
-    },
-    "rollup-plugin-commonjs": {
-      "version": "9.3.4",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.3.4.tgz",
-      "integrity": "sha512-DTZOvRoiVIHHLFBCL4pFxOaJt8pagxsVldEXBOn6wl3/V21wVaj17HFfyzTsQUuou3sZL3lEJZVWKPFblJfI6w==",
-      "dev": true,
-      "requires": {
-        "estree-walker": "^0.6.0",
-        "magic-string": "^0.25.2",
-        "resolve": "^1.10.0",
-        "rollup-pluginutils": "^2.6.0"
-      }
-    },
-    "rollup-plugin-node-resolve": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.2.4.tgz",
-      "integrity": "sha512-t/64I6l7fZ9BxqD3XlX4ZeO6+5RLKyfpwE2CiPNUKa+GocPlQhf/C208ou8y3AwtNsc6bjSk/8/6y/YAyxCIvw==",
-      "dev": true,
-      "requires": {
-        "@types/resolve": "0.0.8",
-        "builtin-modules": "^3.1.0",
-        "is-module": "^1.0.0",
-        "resolve": "^1.10.0"
-      }
-    },
-    "rollup-pluginutils": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
-      "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
-      "dev": true,
-      "requires": {
-        "estree-walker": "^0.6.1"
-      },
-      "dependencies": {
-        "estree-walker": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
-          "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
-          "dev": true
-        }
+        "fsevents": "~2.1.2"
       }
     },
     "rsvp": {
@@ -6392,7 +6323,8 @@
     "safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
     },
     "sander": {
       "version": "0.5.1",
@@ -6707,15 +6639,16 @@
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+      "dev": true
     },
     "saxes": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
-      "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
+      "version": "3.1.11",
+      "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
+      "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
       "dev": true,
       "requires": {
-        "xmlchars": "^2.2.0"
+        "xmlchars": "^2.1.1"
       }
     },
     "scss-tokenizer": {
@@ -6742,12 +6675,14 @@
     "semver": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
-      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
+      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+      "dev": true
     },
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
     },
     "set-value": {
       "version": "2.0.1",
@@ -6797,17 +6732,20 @@
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
     },
     "simple-concat": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
-      "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
+      "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=",
+      "dev": true
     },
     "simple-get": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
       "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
+      "dev": true,
       "requires": {
         "decompress-response": "^4.2.0",
         "once": "^1.3.1",
@@ -7057,21 +6995,10 @@
       }
     },
     "stack-utils": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz",
-      "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==",
-      "dev": true,
-      "requires": {
-        "escape-string-regexp": "^2.0.0"
-      },
-      "dependencies": {
-        "escape-string-regexp": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
-          "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
-          "dev": true
-        }
-      }
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
+      "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==",
+      "dev": true
     },
     "static-extend": {
       "version": "0.1.2",
@@ -7110,28 +7037,28 @@
       "dev": true
     },
     "string-length": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
-      "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz",
+      "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==",
       "dev": true,
       "requires": {
-        "char-regex": "^1.0.2",
-        "strip-ansi": "^6.0.0"
+        "astral-regex": "^1.0.0",
+        "strip-ansi": "^5.2.0"
       },
       "dependencies": {
         "ansi-regex": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
-          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
           "dev": true
         },
         "strip-ansi": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
-          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
           "dev": true,
           "requires": {
-            "ansi-regex": "^5.0.0"
+            "ansi-regex": "^4.1.0"
           }
         }
       }
@@ -7140,6 +7067,7 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
       "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "dev": true,
       "requires": {
         "is-fullwidth-code-point": "^2.0.0",
         "strip-ansi": "^4.0.0"
@@ -7187,6 +7115,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
       "requires": {
         "safe-buffer": "~5.1.0"
       }
@@ -7195,6 +7124,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
       "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+      "dev": true,
       "requires": {
         "ansi-regex": "^3.0.0"
       },
@@ -7202,7 +7132,8 @@
         "ansi-regex": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
         }
       }
     },
@@ -7239,7 +7170,8 @@
     "strip-json-comments": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true
     },
     "supports-color": {
       "version": "5.5.0",
@@ -7402,12 +7334,12 @@
       }
     },
     "tr46": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
-      "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+      "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
       "dev": true,
       "requires": {
-        "punycode": "^2.1.1"
+        "punycode": "^2.1.0"
       }
     },
     "trim-newlines": {
@@ -7623,7 +7555,8 @@
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
     },
     "uuid": {
       "version": "3.4.0",
@@ -7680,11 +7613,13 @@
       }
     },
     "w3c-xmlserializer": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
-      "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
+      "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
       "dev": true,
       "requires": {
+        "domexception": "^1.0.1",
+        "webidl-conversions": "^4.0.2",
         "xml-name-validator": "^3.0.0"
       }
     },
@@ -7698,9 +7633,9 @@
       }
     },
     "webidl-conversions": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
-      "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+      "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
       "dev": true
     },
     "whatwg-encoding": {
@@ -7719,22 +7654,14 @@
       "dev": true
     },
     "whatwg-url": {
-      "version": "8.1.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz",
-      "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+      "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
       "dev": true,
       "requires": {
         "lodash.sortby": "^4.7.0",
-        "tr46": "^2.0.2",
-        "webidl-conversions": "^5.0.0"
-      },
-      "dependencies": {
-        "webidl-conversions": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
-          "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==",
-          "dev": true
-        }
+        "tr46": "^1.0.1",
+        "webidl-conversions": "^4.0.2"
       }
     },
     "which": {
@@ -7769,6 +7696,7 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
       "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "dev": true,
       "requires": {
         "string-width": "^1.0.2 || 2"
       }
@@ -7806,15 +7734,6 @@
             "color-convert": "^2.0.1"
           }
         },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
         "emoji-regex": {
           "version": "8.0.0",
           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -7852,7 +7771,8 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
     },
     "write-file-atomic": {
       "version": "3.0.3",
@@ -7867,9 +7787,9 @@
       }
     },
     "ws": {
-      "version": "7.3.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
-      "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==",
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
+      "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
       "dev": true
     },
     "xml-name-validator": {
@@ -7897,9 +7817,9 @@
       "dev": true
     },
     "yargs": {
-      "version": "15.3.1",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
-      "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
       "dev": true,
       "requires": {
         "cliui": "^6.0.0",
@@ -7912,7 +7832,7 @@
         "string-width": "^4.2.0",
         "which-module": "^2.0.0",
         "y18n": "^4.0.0",
-        "yargs-parser": "^18.1.1"
+        "yargs-parser": "^18.1.2"
       },
       "dependencies": {
         "ansi-regex": {
diff --git a/ui/package.json b/ui/package.json
index 7baa4cd..22573c1 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -10,19 +10,18 @@
     "@tsundoku/micromodal_types": "^0.0.1",
     "@types/chrome": "0.0.86",
     "@types/color-convert": "^1.9.0",
+    "@types/gtag.js": "0.0.3",
     "@types/mithril": "^1.1.17",
     "@types/node": "^14.0.10",
     "@types/pako": "^1.0.1",
     "@types/uuid": "^3.4.9",
     "@types/w3c-web-usb": "^1.0.4",
     "bufferutil": "^4.0.1",
-    "canvas": "^2.6.1",
     "color-convert": "^2.0.1",
     "custom_utils": "file:src/base/utils",
     "devtools-protocol": "0.0.681549",
     "events": "^3.1.0",
     "immer": "^1.12.1",
-    "jsbn-rsa": "^1.0.3",
     "micromodal": "^0.4.6",
     "mithril": "^1.1.7",
     "noice-json-rpc": "^1.2.0",
@@ -33,18 +32,19 @@
     "uuid": "^3.4.0"
   },
   "devDependencies": {
+    "@rollup/plugin-commonjs": "^14.0.0",
+    "@rollup/plugin-node-resolve": "^8.4.0",
     "@types/jest": "^22.2.3",
     "@types/puppeteer": "^1.20.6",
     "dingusjs": "^0.0.3",
     "jest": "^25.5.4",
     "node-sass": "^4.14.1",
     "puppeteer": "^1.20.0",
-    "rollup": "^1.32.1",
-    "rollup-plugin-commonjs": "^9.3.4",
-    "rollup-plugin-node-resolve": "^4.2.4",
+    "rollup": "^2.3.4",
     "sorcery": "^0.10.0",
     "tslib": "^1.13.0",
     "tslint": "^5.20.1",
-    "typescript": "^3.9.3"
+    "typescript": "^3.9.3",
+    "canvas": "^2.6.1"
   }
 }
diff --git a/ui/rollup.config.js b/ui/rollup.config.js
index d6389d2..421e494 100644
--- a/ui/rollup.config.js
+++ b/ui/rollup.config.js
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import commonjs from 'rollup-plugin-commonjs';
-import nodeResolve from 'rollup-plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import nodeResolve from '@rollup/plugin-node-resolve';
 
 export default {
   output: {name: 'perfetto'},
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 16fe564..f6f5fa5 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -231,6 +231,15 @@
     color: #333;
 }
 
+.privacy {
+  text-decoration: none;
+  font-family: 'Raleway', sans-serif;
+  color: #333;
+  position: absolute;
+  bottom: 60px;
+  font-size: 15px;
+}
+
 .query-table-container {
   width: 100%;
   overflow-x: auto;
@@ -534,6 +543,7 @@
   --collapsed-background: hsla(190, 49%, 97%, 1);
   --collapsed-transparent: hsla(190, 49%, 97%, 0);
   --expanded-background: hsl(215, 22%, 19%);
+  --expanded-transparent: hsl(215, 22%, 19%, 0);
   display: grid;
   grid-template-columns: auto 1fr;
   grid-template-rows: 1fr;
@@ -547,7 +557,7 @@
     margin-top: -1px;
   }
   &[collapsed=true] {
-    background-color: var(--collapsed-background-transparent);
+    background-color: var(--collapsed-transparent);
     .shell {
       border-right: 1px solid #c7d0db;
       background-color: var(--collapsed-background);
@@ -557,7 +567,7 @@
     };
   }
   &[collapsed=false] {
-    background-color: var(--expanded-background);
+    background-color: var(--expanded-transparent);
     color: white;
     font-weight: bold;
     .shell.flash {
@@ -604,3 +614,43 @@
 .time-selection-panel {
   height: 10px;
 }
+
+
+.cookie-consent {
+  position: absolute;
+  z-index: 10;
+  left: 10px;
+  bottom: 10px;
+  width: 550px;
+  background-color:#19212b;
+  font-size: 14px;
+  color:rgb(180, 183, 186);
+  border-radius: 5px;
+  padding: 20px;
+
+  .buttons {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 10px;
+    font-size: 15px
+  }
+
+  button {
+    padding: 10px;
+    border-radius: 3px;
+    color: #fff;
+    margin-left: 5px;
+    a {
+      text-decoration: none;
+      color: #fff;
+    }
+  }
+  button:hover {
+    background-color: #373f4b;
+    cursor: pointer;
+  }
+}
+
+.disallow-selection {
+  user-select: none;
+}
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index 757062b..7fc38ba 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -125,12 +125,36 @@
     background: white;
     &.aggregation {
       padding-top: 5px;
-      display: block;
+      display: grid;
+      grid-template-areas: "description range"
+                            "heading heading";
+      grid-template-columns: 1fr auto;
+      .states {
+        font-size: 11px;
+        margin: 0 10px 2px 10px;
+        display: flex;
+        overflow: hidden;
+        .state {
+          height: 20px;
+          line-height: 20px;
+          padding-left: 3px;
+          padding-right: 3px;
+          border-left: white 1px solid;
+          &:hover {
+            min-width: fit-content;
+          }
+        }
+
+      }
       .time-range {
         text-align: right;
         font-size: 11px;
+        font-weight: 400;
         margin-right: 5px;
       }
+      table {
+        grid-area: heading;
+      }
       th {
         cursor: pointer;
         .material-icons {
@@ -231,6 +255,52 @@
   }
 }
 
+.flow-link:hover {
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.flow-info i.material-icons {
+  color: rgb(60, 86, 136);
+}
+
+.warning {
+  position: relative;
+  font-size: 13px;
+  color: hsl(45, 100%, 48%);
+}
+
+.warning i.material-icons {
+  font-size: 13px;
+}
+
+.warning .tooltip {
+  visibility: hidden;
+
+  background-color: white;
+  color: #3f4040;
+  box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3);
+
+  padding: 4px;
+  border-radius: 4px;
+
+  text-align: center;
+  white-space: nowrap;
+
+  position: absolute;
+  z-index: 1;
+  top: -5px;
+  left: 105%;
+}
+
+.warning:hover .tooltip {
+  visibility: visible;
+}
+
+.flow-button {
+  color: rgb(60, 86, 136);
+}
+
 table.half-width {
   max-width: 50%;
 }
@@ -280,16 +350,28 @@
   }
 }
 
+.sum {
+  font-weight: bolder;
+  font-size: 12px;
+  .sum-data {
+    border-bottom: 1px solid rgba(60, 76, 92, 0.4);
+  }
+}
+
 .log-panel {
   width: 100%;
   height: 100%;
   display: grid;
   grid-template-rows: auto 1fr;
+  font-family: 'Roboto Condensed', sans-serif;
 
   header {
     position: sticky;
     top: 0px;
     z-index: 1;
+    background-color: white;
+    color: #3c4b5d;
+    padding: 5px;
   }
 
   header.stale {
diff --git a/ui/src/assets/metrics_page.scss b/ui/src/assets/metrics_page.scss
new file mode 100644
index 0000000..4881d46
--- /dev/null
+++ b/ui/src/assets/metrics_page.scss
@@ -0,0 +1,49 @@
+// 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.
+
+.metrics-page {
+  padding: 30px;
+  font-family: 'Raleway', sans-serif;
+  overflow-y: auto;
+
+  .metric-run-button {
+    background-color: #262f3c;
+    color: #fff;
+    border-radius: 4px;
+    padding: 5px 10px;
+    font-weight: bold;
+    font-family: 'Raleway';
+  }
+
+  select {
+    margin: 10px;
+    font-family: 'Raleway';
+    font-size: 1em;
+    border: 1px solid black;
+    background-color: #eee;
+  }
+
+  pre {
+    background-color: #eee;
+    padding: 20px;
+    font-family: 'Roboto Mono';
+    line-height: 1.5em;
+    border-radius: 5px;
+    user-select: text;
+    overflow-x: auto;
+    &.metric-error {
+      color: #EF6C00;
+    }
+  }
+}
diff --git a/ui/src/assets/modal.scss b/ui/src/assets/modal.scss
index 4a63cc5..0f54b00 100644
--- a/ui/src/assets/modal.scss
+++ b/ui/src/assets/modal.scss
@@ -186,6 +186,8 @@
 
 .modal-pre {
   white-space: pre-line;
+  font-size: 13px;
+  user-select: text;
 }
 
 .modal-logs, .modal-bash {
@@ -196,6 +198,7 @@
   font-family: monospace;
   -webkit-user-select: text;
   margin-top: 10px;
+  margin-bottom: 10px;
   min-height: 50px;
   max-height: 40vh;
   overflow: scroll;
@@ -207,3 +210,14 @@
   overflow: auto;
   min-height: 0;
 }
+
+.modal-textarea {
+  display: block;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  width: 100%;
+}
+
+.modal-small {
+  font-size: 11px;
+}
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index b30b690..05de317 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -15,6 +15,7 @@
 @import 'typefaces';
 @import 'common';
 @import 'analyze_page';
+@import 'metrics_page';
 @import 'sidebar';
 @import 'topbar';
 @import 'record';
diff --git a/ui/src/assets/rec_cpu_wakeup.png b/ui/src/assets/rec_cpu_wakeup.png
deleted file mode 100644
index 0792a63..0000000
--- a/ui/src/assets/rec_cpu_wakeup.png
+++ /dev/null
Binary files differ
diff --git a/ui/src/assets/rec_gpu_mem_total.png b/ui/src/assets/rec_gpu_mem_total.png
new file mode 100644
index 0000000..4b5a44a
--- /dev/null
+++ b/ui/src/assets/rec_gpu_mem_total.png
Binary files differ
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index 4f98127..02dab44 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -152,9 +152,9 @@
   }
 
   .note {
-    border: 1px dashed #ddd;
+    border-radius: 3px;
+    margin-bottom: 5px;
     background: #f9eeba;
-    margin: var(--record-section-padding);
     padding: 10px;
     font-family: 'Roboto', sans-serif;
     font-size: 14px;
@@ -280,10 +280,88 @@
   }
 
   &.active {
-    display: grid;
+    display: block;
     opacity: 1;
   }
 
+  .config {
+    &:nth-of-type(2n) {
+      background-color: #e7e7e7;
+    }
+
+    height: auto;
+    width: 100%;
+    padding: 0px;
+    display: flex;
+    align-items: center;
+  }
+
+  .title-config {
+    display: inline-block;
+    margin: var(--record-section-padding);
+    flex-grow: 1;
+    word-break: break-all;
+  }
+
+  .config-button {
+    border-radius: 10px;
+    margin-right: 10px;
+    text-align: center;
+    justify-items: center;
+    font-family: 'Raleway', sans-serif;
+    padding: 7px;
+    width: 75px;
+
+    &:hover {
+      box-shadow: 0 0 4px 0px #999;
+    }
+
+    &.load {
+      background-color: hsl(88, 50%, 67%);
+    }
+
+    &.delete {
+      background-color: hsl(0, 50%, 67%);
+    }
+
+    &.save {
+      width: 160px;
+      background-color: hsl(197, 50%, 67%);
+    }
+  }
+
+  .input-config {
+    margin-top: 20px;
+    margin-bottom: 20px;
+    display: flex;
+    align-items: center;
+    padding: 0px;
+
+    input {
+      border-radius: 20px;
+      border-color: #0000003d;
+      line-height: 36px;
+      padding: 0 10px;
+      font-size: 18px;
+      font-family: 'Roboto Condensed', sans-serif;
+      font-weight: 300;
+      color: #666;
+      flex-grow: 1;
+      margin-right: 10px;
+      margin-left: 10px;
+
+      background-color: transparent;
+      &:focus {
+        outline: none;
+      }
+      &::placeholder {
+        color: #b4b7ba;
+        font-family: 'Raleway', sans-serif;
+        font-weight: 400;
+      }
+    }
+  }
+
   // By default space all section elements by the same amount.
   --record-section-padding: 20px;
 
@@ -872,6 +950,22 @@
       }
     }
 
+    .permalinkconfig {
+      margin: var(--record-section-padding);
+      height: 40px;
+      max-width: 200px;
+      border-radius: 10px;
+      text-align: center;
+      justify-items: center;
+      font-family: 'Raleway', sans-serif;
+      padding: 7px;
+      background-color: hsl(88, 50%, 67%);
+
+      &:hover {
+        box-shadow: 0 0 4px 0px #999;
+      }
+    }
+
     progress {
       -webkit-appearance: none;
       appearance: none;
@@ -888,3 +982,28 @@
     }
   }
 }  // record-section
+
+.inline-chip { 
+  @include transition();
+  &:hover, &:active {
+    box-shadow: 0 0 2px 0px #ccc;
+    background-color: #fafafa;
+  }
+
+  >i.material-icons {
+    color: rgb(60, 60, 60);
+    font-size: 14px;
+  }
+
+  line-height: 25px;
+  font-size: smaller;
+  padding: 2px 4px;
+  border: 1px solid #eee;
+  margin: 2px;
+  border-radius: 9px;
+}
+
+a.inline-chip, a.inline-chip:link, a.inline-chip:visited {
+  text-decoration: none;
+  color: var(--record-text-color);
+}
diff --git a/ui/src/assets/sidebar.scss b/ui/src/assets/sidebar.scss
index a25b715..b368214 100644
--- a/ui/src/assets/sidebar.scss
+++ b/ui/src/assets/sidebar.scss
@@ -50,7 +50,6 @@
       background-color: #262f3c;
       height: var(--topbar-height);
       left: calc(var(--sidebar-width) - 50px);
-      padding-top: 3px;
       border-radius: 0 5px 5px 0;
       border-bottom: inherit;
       visibility: visible;  // So stays visible when the sidebar is hidden.
@@ -183,6 +182,7 @@
     .sidebar-footer {
       position: absolute;
       bottom: 0;
+      width: 100%;
       padding: 2px 10px;
       display: grid;
       height: - var(--sidebar-padding-bottom);
@@ -227,6 +227,19 @@
           line-height: 11px;
         }
       }
+
+      .version {
+        position: absolute;
+        right: 8px;
+        bottom: 3px;
+        font-size: 12px;
+        font-family: 'Roboto Condensed', 'Roboto', sans-serif;
+        a {
+          color: rgba(255, 255, 255, 0.5);
+          text-decoration: none;
+        }
+        margin-top: 11px;
+      }
     }
 }
 
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
index 71d6917..4ab3907 100644
--- a/ui/src/assets/topbar.scss
+++ b/ui/src/assets/topbar.scss
@@ -36,7 +36,7 @@
         border-radius: 20px;
         background-color: #fcfcfc;
         border: 0;
-        line-height: 36px;
+        line-height: 34px;
         &:before {
             @include material-icon('search');
             margin: 5px;
@@ -62,7 +62,6 @@
             }
         }
         &.command-mode {
-            // background-color: #354252;
             background-color: #111;
             border-radius: 0;
             width: 100%;
@@ -224,3 +223,9 @@
   padding: 3px;
   border-radius: 3px;
 }
+
+.hide-sidebar {
+  .command-mode {
+    padding-left: 48px;
+  }
+}
diff --git a/ui/src/assets/trace_info_page.scss b/ui/src/assets/trace_info_page.scss
index eafe253..38c6f9d 100644
--- a/ui/src/assets/trace_info_page.scss
+++ b/ui/src/assets/trace_info_page.scss
@@ -16,6 +16,7 @@
   overflow-y: auto;
   overflow-x: hidden;
   padding: 0 20px;
+  user-select: text;
 
   section {
     margin: 20px auto;
@@ -28,6 +29,13 @@
       background-color: #F3E5F5;
     }
 
+    .metric-error {
+      font-family: var(--monospace-font);
+      font-size: 12px;
+      padding: 5px;
+      word-break: break-all;
+    }
+
     h2 {
       font-family: 'Raleway', sans-serif;
       font-weight: 400;
@@ -74,6 +82,7 @@
           font-size: 12px;
           padding: 5px;
           word-break: break-all;
+          white-space: pre-wrap;
           &:first-of-type {
             font-weight: 800;
           }
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 25c37d5..75a02ca 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import * as version from '../gen/perfetto_version';
+
 export type ErrorHandler = (err: string) => void;
 
 let errorHandler: ErrorHandler = (_: string) => {};
@@ -55,8 +57,10 @@
     errLog += '\n';
     errLog += errStack !== undefined ? errStack : JSON.stringify(errorObj);
   }
-  errLog += `\n\nUA: ${navigator.userAgent}\n`;
+  errLog += '\n\n';
+  errLog += `${version.VERSION} ${version.SCM_REVISION}\n`;
+  errLog += `UA: ${navigator.userAgent}\n`;
 
   console.error(errLog, err);
   errorHandler(errLog);
-}
\ No newline at end of file
+}
diff --git a/ui/src/base/string_utils.ts b/ui/src/base/string_utils.ts
index efe8fb0..bf44d97 100644
--- a/ui/src/base/string_utils.ts
+++ b/ui/src/base/string_utils.ts
@@ -12,23 +12,87 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-export function uint8ArrayToBase64(buffer: Uint8Array): string {
-  return btoa(uint8ArrayToString(buffer));
+import {
+  decode as b64Decode,
+  encode as b64Encode,
+  length as b64Len
+} from '@protobufjs/base64';
+import {
+  length as utf8Len,
+  read as utf8Read,
+  write as utf8Write
+} from '@protobufjs/utf8';
+
+import {assertTrue} from './logging';
+
+// TextDecoder/Decoder requires the full DOM and isn't available in all types
+// of tests. Use fallback implementation from protbufjs.
+let UTF8Decoder: {decode: (buf: Uint8Array) => string;};
+let UTF8Encoder: {encode: (str: string) => Uint8Array;};
+try {
+  UTF8Decoder = new TextDecoder('utf-8');
+  UTF8Encoder = new TextEncoder();
+} catch (_) {
+  if (typeof process === 'undefined') {
+    // Silence the warning when we know we are running under NodeJS.
+    console.warn(
+        'Using fallback UTF8 Encoder/Decoder, This should happen only in ' +
+        'tests and NodeJS-based environments, not in browsers.');
+  }
+  UTF8Decoder = {decode: (buf: Uint8Array) => utf8Read(buf, 0, buf.length)};
+  UTF8Encoder = {
+    encode: (str: string) => {
+      const arr = new Uint8Array(utf8Len(str));
+      const written = utf8Write(str, arr, 0);
+      assertTrue(written === arr.length);
+      return arr;
+    }
+  };
 }
 
-// This function will not handle correctly buffers with a large number of
-// elements due to a js limitation on the number of arguments of a function.
-// The apply will in fact do a call like
-// 'String.fromCharCode(buffer[0],buffer[1],...)'.
-export function uint8ArrayToString(buffer: Uint8Array): string {
-  return String.fromCharCode.apply(null, Array.from(buffer));
+export function base64Encode(buffer: Uint8Array): string {
+  return b64Encode(buffer, 0, buffer.length);
 }
 
-export function stringToUint8Array(str: string): Uint8Array {
-  const bufView = new Uint8Array(new ArrayBuffer(str.length));
+export function base64Decode(str: string): Uint8Array {
+  const arr = new Uint8Array(b64Len(str));
+  const written = b64Decode(str, arr, 0);
+  assertTrue(written === arr.length);
+  return arr;
+}
+
+export function utf8Encode(str: string): Uint8Array {
+  return UTF8Encoder.encode(str);
+}
+
+// Note: not all byte sequences can be converted to<>from UTF8. This can be
+// used only with valid unicode strings, not arbitrary byte buffers.
+export function utf8Decode(buffer: Uint8Array): string {
+  return UTF8Decoder.decode(buffer);
+}
+
+// The binaryEncode/Decode functions below allow to encode an arbitrary binary
+// buffer into a string that can be JSON-encoded. binaryEncode() applies
+// UTF-16 encoding to each byte individually.
+// Unlike utf8Encode/Decode, any arbitrary byte sequence can be converted into a
+// valid string, and viceversa.
+// This should be only used when a byte array needs to be transmitted over an
+// interface that supports only JSON serialization (e.g., postmessage to a
+// chrome extension).
+
+export function binaryEncode(buf: Uint8Array): string {
+  let str = '';
+  for (let i = 0; i < buf.length; i++) {
+    str += String.fromCharCode(buf[i]);
+  }
+  return str;
+}
+
+export function binaryDecode(str: string): Uint8Array {
+  const buf = new Uint8Array(str.length);
   const strLen = str.length;
   for (let i = 0; i < strLen; i++) {
-    bufView[i] = str.charCodeAt(i);
+    buf[i] = str.charCodeAt(i);
   }
-  return bufView;
-}
\ No newline at end of file
+  return buf;
+}
diff --git a/ui/src/base/string_utils_jsdomtest.ts b/ui/src/base/string_utils_jsdomtest.ts
deleted file mode 100644
index e8bae93..0000000
--- a/ui/src/base/string_utils_jsdomtest.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import {
-  stringToUint8Array,
-  uint8ArrayToBase64,
-  uint8ArrayToString
-} from './string_utils';
-
-test('uint8ArrayToBase64', () => {
-  const bytes = [...'Hello, world'].map(c => c.charCodeAt(0));
-  const buffer = new Uint8Array(bytes);
-  expect(uint8ArrayToBase64(buffer)).toEqual('SGVsbG8sIHdvcmxk');
-});
-
-test('stringToBufferToString', () => {
-  const testString = 'Hello world!';
-  const buffer = stringToUint8Array(testString);
-  const convertedBack = uint8ArrayToString(buffer);
-  expect(testString).toEqual(convertedBack);
-});
-
-test('bufferToStringToBuffer', () => {
-  const bytes = [...'Hello, world'].map(c => c.charCodeAt(0));
-  const buffer = new Uint8Array(bytes);
-  const toString = uint8ArrayToString(buffer);
-  const convertedBack = stringToUint8Array(toString);
-  expect(convertedBack).toEqual(buffer);
-});
diff --git a/ui/src/base/string_utils_unittest.ts b/ui/src/base/string_utils_unittest.ts
new file mode 100644
index 0000000..c8dce8e
--- /dev/null
+++ b/ui/src/base/string_utils_unittest.ts
@@ -0,0 +1,75 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  base64Decode,
+  base64Encode,
+  binaryDecode,
+  binaryEncode,
+  utf8Decode,
+  utf8Encode,
+} from './string_utils';
+
+test('string_utils.stringToBase64', () => {
+  const bytes = [...'Hello, world'].map(c => c.charCodeAt(0));
+  const buffer = new Uint8Array(bytes);
+  const b64Encoded = base64Encode(buffer);
+  expect(b64Encoded).toEqual('SGVsbG8sIHdvcmxk');
+  expect(base64Decode(b64Encoded)).toEqual(buffer);
+});
+
+test('string_utils.bufferToBase64', () => {
+  const buffer = new Uint8Array([0xff, 0, 0, 0x81, 0x2a, 0xfe]);
+  const b64Encoded = base64Encode(buffer);
+  expect(b64Encoded).toEqual('/wAAgSr+');
+  expect(base64Decode(b64Encoded)).toEqual(buffer);
+});
+
+test('string_utils.utf8EncodeAndDecode', () => {
+  const testString = '¡HéllØ wörld!';
+  const buffer = utf8Encode(testString);
+  expect(buffer).toEqual(new Uint8Array([
+    194,
+    161,
+    72,
+    195,
+    169,
+    108,
+    108,
+    195,
+    152,
+    32,
+    119,
+    195,
+    182,
+    114,
+    108,
+    100,
+    33
+  ]));
+  expect(utf8Decode(buffer)).toEqual(testString);
+});
+
+test('string_utils.binaryEncodeAndDecode', () => {
+  const buf = new Uint8Array(256 + 4);
+  for (let i = 0; i < 256; i++) {
+    buf[i] = i;
+  }
+  buf.set([0xf0, 0x28, 0x8c, 0xbc], 256);
+  const encodedStr = binaryEncode(buf);
+  expect(encodedStr.length).toEqual(buf.length);
+  const encodedThroughJson = JSON.parse(JSON.stringify(encodedStr));
+  expect(binaryDecode(encodedStr)).toEqual(buf);
+  expect(binaryDecode(encodedThroughJson)).toEqual(buf);
+});
diff --git a/ui/src/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 280068a..be6d752 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -28,8 +28,6 @@
 
 import {DevToolsSocket} from './devtools_socket';
 
-// The chunk size should be large enough to support reasonable batching of data,
-// but small enough not to cause stack overflows in uint8ArrayToString().
 const CHUNK_SIZE: number = 1024 * 1024 * 16;  // 16Mb
 
 export class ChromeTracingController extends RpcConsumerPort {
diff --git a/ui/src/chrome_extension/index.ts b/ui/src/chrome_extension/index.ts
index 6350c84..6a55b21 100644
--- a/ui/src/chrome_extension/index.ts
+++ b/ui/src/chrome_extension/index.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {stringToUint8Array} from '../base/string_utils';
+import {binaryDecode} from '../base/string_utils';
 import {ChromeTracingController} from './chrome_tracing_controller';
 
 let chromeTraceController: ChromeTracingController|undefined = undefined;
@@ -38,7 +38,7 @@
   // ChromeExtensionConsumerPort sends the request data as string because
   // chrome.runtime.port doesn't support ArrayBuffers.
   const requestDataArray: Uint8Array = message.requestData ?
-      stringToUint8Array(message.requestData) :
+      binaryDecode(message.requestData) :
       new Uint8Array();
   chromeTraceController.handleCommand(message.method, requestDataArray);
 }
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index d297813f..7a590ef 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,18 +15,25 @@
 import {Draft} from 'immer';
 
 import {assertExists} from '../base/logging';
-import {
-  Area,
-  CallsiteInfo,
-  HeapProfileFlamegraphViewingOption
-} from '../common/state';
+import {randomColor} from '../common/colorizer';
 import {ConvertTrace, ConvertTraceToPprof} from '../controller/trace_converter';
+import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common';
+import {COUNTER_TRACK_KIND} from '../tracks/counter/common';
+import {DEBUG_SLICE_TRACK_KIND} from '../tracks/debug_slices/common';
+import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common';
+import {
+  PROCESS_SCHEDULING_TRACK_KIND
+} from '../tracks/process_scheduling/common';
+import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
 
 import {DEFAULT_VIEWING_OPTION} from './flamegraph_util';
 import {
   AdbRecordingTarget,
+  Area,
+  CallsiteInfo,
   createEmptyState,
   EngineMode,
+  HeapProfileFlamegraphViewingOption,
   LogsPagination,
   NewEngineMode,
   OmniboxState,
@@ -35,7 +42,6 @@
   SCROLLING_TRACK_GROUP,
   State,
   Status,
-  TimestampedAreaSelection,
   TraceSource,
   TraceTime,
   TrackState,
@@ -49,6 +55,7 @@
   engineId: string;
   kind: string;
   name: string;
+  isMainThread?: boolean;
   trackGroup?: string;
   config: {};
 }
@@ -64,6 +71,7 @@
   const recordConfig = state.recordConfig;
   const route = state.route;
   const recordingTarget = state.recordingTarget;
+  const updateChromeCategories = state.updateChromeCategories;
   const extensionInstalled = state.extensionInstalled;
   const availableAdbDevices = state.availableAdbDevices;
   const chromeCategories = state.chromeCategories;
@@ -74,6 +82,7 @@
   state.recordConfig = recordConfig;
   state.route = route;
   state.recordingTarget = recordingTarget;
+  state.updateChromeCategories = updateChromeCategories;
   state.extensionInstalled = extensionInstalled;
   state.availableAdbDevices = availableAdbDevices;
   state.chromeCategories = chromeCategories;
@@ -162,7 +171,7 @@
 
   addTrack(state: StateDraft, args: {
     id?: string; engineId: string; kind: string; name: string;
-    trackGroup?: string; config: {};
+    trackGroup?: string; config: {}; isMainThread: boolean;
   }): void {
     const id = args.id !== undefined ? args.id : `${state.nextId++}`;
     state.tracks[id] = {
@@ -170,6 +179,7 @@
       engineId: args.engineId,
       kind: args.kind,
       name: args.name,
+      isMainThread: args.isMainThread,
       trackGroup: args.trackGroup,
       config: args.config,
     };
@@ -189,11 +199,81 @@
         collapsed: boolean;
       }): void {
     state.trackGroups[args.id] = {
-      ...args,
-      tracks: [],
+      engineId: args.engineId,
+      name: args.name,
+      id: args.id,
+      collapsed: args.collapsed,
+      tracks: [args.summaryTrackId],
     };
   },
 
+  addDebugTrack(state: StateDraft, args: {engineId: string, name: string}):
+      void {
+        if (state.debugTrackId !== undefined) return;
+        const trackId = `${state.nextId++}`;
+        state.debugTrackId = trackId;
+        this.addTrack(state, {
+          id: trackId,
+          engineId: args.engineId,
+          kind: DEBUG_SLICE_TRACK_KIND,
+          name: args.name,
+          isMainThread: false,
+          trackGroup: SCROLLING_TRACK_GROUP,
+          config: {
+            maxDepth: 1,
+          }
+        });
+        this.toggleTrackPinned(state, {trackId});
+      },
+
+  removeDebugTrack(state: StateDraft, _: {}): void {
+    const {debugTrackId} = state;
+    if (debugTrackId === undefined) return;
+    delete state.tracks[debugTrackId];
+    state.scrollingTracks =
+        state.scrollingTracks.filter(id => id !== debugTrackId);
+    state.pinnedTracks = state.pinnedTracks.filter(id => id !== debugTrackId);
+    state.debugTrackId = undefined;
+  },
+
+  sortThreadTracks(state: StateDraft, _: {}): void {
+    const threadTrackOrder = [
+      PROCESS_SCHEDULING_TRACK_KIND,
+      PROCESS_SUMMARY_TRACK,
+      HEAP_PROFILE_TRACK_KIND,
+      COUNTER_TRACK_KIND,
+      ASYNC_SLICE_TRACK_KIND
+    ];
+    for (const group of Object.values(state.trackGroups)) {
+      group.tracks.sort((a: string, b: string) => {
+        const aKind = threadTrackOrder.indexOf(state.tracks[a].kind);
+        const bKind = threadTrackOrder.indexOf(state.tracks[b].kind);
+        const aName = state.tracks[a].name.toLocaleLowerCase();
+        const bName = state.tracks[b].name.toLocaleLowerCase();
+        if (aKind === bKind) {
+          if (state.tracks[a].isMainThread && state.tracks[b].isMainThread) {
+            return 0;
+          } else if (state.tracks[a].isMainThread) {
+            return -1;
+          } else if (state.tracks[b].isMainThread) {
+            return 1;
+          }
+          if (aName > bName) {
+            return 1;
+          } else if (aName === bName) {
+            return 0;
+          } else {
+            return -1;
+          }
+        } else {
+          if (aKind === -1) return 1;
+          if (bKind === -1) return -1;
+          return aKind - bKind;
+        }
+      });
+    }
+  },
+
   updateAggregateSorting(
       state: StateDraft, args: {id: string, column: string}) {
     let prefs = state.aggregatePreferences[args.id];
@@ -295,11 +375,23 @@
         trackGroup.collapsed = !trackGroup.collapsed;
       },
 
+  requestTrackReload(state: StateDraft, _: {}) {
+    if (state.lastTrackReloadRequest) {
+      state.lastTrackReloadRequest++;
+    } else {
+      state.lastTrackReloadRequest = 1;
+    }
+  },
+
   setEngineReady(
       state: StateDraft,
       args: {engineId: string; ready: boolean, mode: EngineMode}): void {
-    state.engines[args.engineId].ready = args.ready;
-    state.engines[args.engineId].mode = args.mode;
+    const engine = state.engines[args.engineId];
+    if (engine === undefined) {
+      return;
+    }
+    engine.ready = args.ready;
+    engine.mode = args.mode;
   },
 
   setNewEngineMode(state: StateDraft, args: {mode: NewEngineMode}): void {
@@ -314,8 +406,12 @@
         }
       },
 
-  createPermalink(state: StateDraft, _: {}): void {
-    state.permalink = {requestId: `${state.nextId++}`, hash: undefined};
+  createPermalink(state: StateDraft, args: {isRecordingConfig: boolean}): void {
+    state.permalink = {
+      requestId: `${state.nextId++}`,
+      hash: undefined,
+      isRecordingConfig: args.isRecordingConfig
+    };
   },
 
   setPermalink(state: StateDraft, args: {requestId: string; hash: string}):
@@ -326,10 +422,7 @@
       },
 
   loadPermalink(state: StateDraft, args: {hash: string}): void {
-    state.permalink = {
-      requestId: `${state.nextId++}`,
-      hash: args.hash,
-    };
+    state.permalink = {requestId: `${state.nextId++}`, hash: args.hash};
   },
 
   clearPermalink(state: StateDraft, _: {}): void {
@@ -372,7 +465,7 @@
   addNote(
       state: StateDraft,
       args: {timestamp: number, color: string, isMovie: boolean}): void {
-    const id = `${state.nextId++}`;
+    const id = `${state.nextNoteId++}`;
     state.notes[id] = {
       noteType: 'DEFAULT',
       id,
@@ -386,21 +479,55 @@
     this.selectNote(state, {id});
   },
 
-  addAreaNote(
-      state: StateDraft, args: {timestamp: number, area: Area, color: string}):
+  markCurrentArea(
+      state: StateDraft, args: {color: string, persistent: boolean}):
       void {
-        const id = `${state.nextId++}`;
+        if (state.currentSelection === null ||
+            state.currentSelection.kind !== 'AREA') {
+          return;
+        }
+        const id = args.persistent ? `${state.nextNoteId++}` : '0';
+        const color = args.persistent ? args.color : '#344596';
         state.notes[id] = {
           noteType: 'AREA',
           id,
-          timestamp: args.timestamp,
-          area: args.area,
-          color: args.color,
+          areaId: state.currentSelection.areaId,
+          color,
           text: '',
         };
-        this.selectNote(state, {id});
+        state.currentSelection.noteId = id;
       },
 
+  toggleMarkCurrentArea(state: StateDraft, args: {persistent: boolean}) {
+    const selection = state.currentSelection;
+    if (selection != null && selection.kind === 'AREA' &&
+        selection.noteId !== undefined) {
+      this.removeNote(state, {id: selection.noteId});
+    } else {
+      const color = randomColor();
+      this.markCurrentArea(state, {color, persistent: args.persistent});
+    }
+  },
+
+  markArea(state: StateDraft, args: {area: Area, persistent: boolean}): void {
+    const areaId = `${state.nextAreaId++}`;
+    state.areas[areaId] = {
+      id: areaId,
+      startSec: args.area.startSec,
+      endSec: args.area.endSec,
+      tracks: args.area.tracks
+    };
+    const id = args.persistent ? `${state.nextNoteId++}` : '0';
+    const color = args.persistent ? randomColor() : '#344596';
+    state.notes[id] = {
+      noteType: 'AREA',
+      id,
+      areaId,
+      color,
+      text: '',
+    };
+  },
+
   toggleVideo(state: StateDraft, _: {}): void {
     state.videoEnabled = !state.videoEnabled;
     if (!state.videoEnabled) {
@@ -443,16 +570,23 @@
   },
 
   removeNote(state: StateDraft, args: {id: string}): void {
+    if (state.notes[args.id] === undefined) return;
     if (state.notes[args.id].noteType === 'MOVIE') {
       state.videoNoteIds = state.videoNoteIds.filter(id => {
         return id !== args.id;
       });
     }
     delete state.notes[args.id];
+    // For regular notes, we clear the current selection but for an area note
+    // we only want to clear the note/marking and leave the area selected.
     if (state.currentSelection === null) return;
     if (state.currentSelection.kind === 'NOTE' &&
         state.currentSelection.id === args.id) {
       state.currentSelection = null;
+    } else if (
+        state.currentSelection.kind === 'AREA' &&
+        state.currentSelection.noteId === args.id) {
+      state.currentSelection.noteId = undefined;
     }
   },
 
@@ -487,11 +621,6 @@
       ts: args.ts,
       type: args.type,
     };
-  },
-
-  showHeapProfileFlamegraph(
-      state: StateDraft,
-      args: {id: number, upid: number, ts: number, type: string}): void {
     state.currentHeapProfileFlamegraph = {
       kind: 'HEAP_PROFILE_FLAMEGRAPH',
       id: args.id,
@@ -543,24 +672,14 @@
         };
       },
 
-  selectThreadState(state: StateDraft, args: {
-    utid: number,
-    ts: number,
-    dur: number,
-    state: string,
-    cpu: number,
-    trackId: string
-  }): void {
-    state.currentSelection = {
-      kind: 'THREAD_STATE',
-      utid: args.utid,
-      ts: args.ts,
-      dur: args.dur,
-      state: args.state,
-      cpu: args.cpu,
-      trackId: args.trackId,
-    };
-  },
+  selectThreadState(state: StateDraft, args: {id: number, trackId: string}):
+      void {
+        state.currentSelection = {
+          kind: 'THREAD_STATE',
+          id: args.id,
+          trackId: args.trackId,
+        };
+      },
 
   deselect(state: StateDraft, _: {}): void {
     state.currentSelection = null;
@@ -597,6 +716,10 @@
     state.recordingTarget = args.target;
   },
 
+  setUpdateChromeCategories(state: StateDraft, args: {update: boolean}): void {
+    state.updateChromeCategories = args.update;
+  },
+
   setAvailableAdbDevices(
       state: StateDraft, args: {devices: AdbRecordingTarget[]}): void {
     state.availableAdbDevices = args.devices;
@@ -606,8 +729,61 @@
     state.frontendLocalState.omniboxState = args;
   },
 
-  selectArea(state: StateDraft, args: TimestampedAreaSelection): void {
-    state.frontendLocalState.selectedArea = args;
+  selectArea(state: StateDraft, args: {area: Area}): void {
+    const areaId = `${state.nextAreaId++}`;
+    state.areas[areaId] = {
+      id: areaId,
+      startSec: args.area.startSec,
+      endSec: args.area.endSec,
+      tracks: args.area.tracks
+    };
+    state.currentSelection = {kind: 'AREA', areaId};
+  },
+
+  editArea(state: StateDraft, args: {area: Area, areaId: string}): void {
+    state.areas[args.areaId] = {
+      id: args.areaId,
+      startSec: args.area.startSec,
+      endSec: args.area.endSec,
+      tracks: args.area.tracks
+    };
+  },
+
+  reSelectArea(state: StateDraft, args: {areaId: string, noteId: string}):
+      void {
+        state.currentSelection = {
+          kind: 'AREA',
+          areaId: args.areaId,
+          noteId: args.noteId
+        };
+      },
+
+  toggleTrackSelection(
+      state: StateDraft, args: {id: string, isTrackGroup: boolean}) {
+    const selection = state.currentSelection;
+    if (selection === null || selection.kind !== 'AREA') return;
+    const areaId = selection.areaId;
+    const index = state.areas[areaId].tracks.indexOf(args.id);
+    if (index > -1) {
+      state.areas[areaId].tracks.splice(index, 1);
+      if (args.isTrackGroup) {  // Also remove all child tracks.
+        for (const childTrack of state.trackGroups[args.id].tracks) {
+          const childIndex = state.areas[areaId].tracks.indexOf(childTrack);
+          if (childIndex > -1) {
+            state.areas[areaId].tracks.splice(childIndex, 1);
+          }
+        }
+      }
+    } else {
+      state.areas[areaId].tracks.push(args.id);
+      if (args.isTrackGroup) {  // Also add all child tracks.
+        for (const childTrack of state.trackGroups[args.id].tracks) {
+          if (!state.areas[areaId].tracks.includes(childTrack)) {
+            state.areas[areaId].tracks.push(childTrack);
+          }
+        }
+      }
+    }
   },
 
   setVisibleTraceTime(state: StateDraft, args: VisibleState): void {
@@ -630,7 +806,34 @@
 
   setAnalyzePageQuery(state: StateDraft, args: {query: string}): void {
     state.analyzePageQuery = args.query;
-  }
+  },
+
+  requestSelectedMetric(state: StateDraft, _: {}): void {
+    if (!state.metrics.availableMetrics) throw Error('No metrics available');
+    if (state.metrics.selectedIndex === undefined) {
+      throw Error('No metric selected');
+    }
+    state.metrics.requestedMetric =
+        state.metrics.availableMetrics[state.metrics.selectedIndex];
+  },
+
+  resetMetricRequest(state: StateDraft, args: {name: string}): void {
+    if (state.metrics.requestedMetric !== args.name) return;
+    state.metrics.requestedMetric = undefined;
+  },
+
+  setAvailableMetrics(state: StateDraft, args: {metrics: string[]}): void {
+    state.metrics.availableMetrics = args.metrics;
+    if (args.metrics.length > 0) state.metrics.selectedIndex = 0;
+  },
+
+  setMetricSelectedIndex(state: StateDraft, args: {index: number}): void {
+    if (!state.metrics.availableMetrics ||
+        args.index >= state.metrics.availableMetrics.length) {
+      throw Error('metric selection out of bounds');
+    }
+    state.metrics.selectedIndex = args.index;
+  },
 };
 
 // When we are on the frontend side, we don't really want to execute the
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 686da55..9255a93 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -29,6 +29,7 @@
     engineId: '1',
     kind: 'SOME_TRACK_KIND',
     name: 'A track',
+    isMainThread: false,
     trackGroup: SCROLLING_TRACK_GROUP,
     config: {},
   };
@@ -49,6 +50,7 @@
       engineId: '1',
       kind: 'cpu',
       name: 'Cpu 1',
+      isMainThread: false,
       trackGroup: SCROLLING_TRACK_GROUP,
       config: {},
     });
@@ -58,6 +60,7 @@
       engineId: '2',
       kind: 'cpu',
       name: 'Cpu 2',
+      isMainThread: false,
       trackGroup: SCROLLING_TRACK_GROUP,
       config: {},
     });
@@ -87,12 +90,14 @@
       engineId: '1',
       kind: 'slices',
       name: 'renderer 1',
+      isMainThread: false,
       trackGroup: '123-123-123',
       config: {},
     });
   });
 
-  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('1');
+  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('s');
+  expect(afterTrackAdd.trackGroups['123-123-123'].tracks[1]).toBe('1');
 });
 
 test('reorder tracks', () => {
@@ -101,12 +106,14 @@
       engineId: '1',
       kind: 'cpu',
       name: 'Cpu 1',
+      isMainThread: false,
       config: {},
     });
     StateActions.addTrack(draft, {
       engineId: '2',
       kind: 'cpu',
       name: 'Cpu 2',
+      isMainThread: false,
       config: {},
     });
   });
@@ -267,6 +274,7 @@
       engineId: '1',
       kind: 'cpu',
       name: 'Cpu 1',
+      isMainThread: false,
       config: {},
     });
   });
@@ -285,3 +293,24 @@
   expect(thrice.scrollingTracks.length).toBe(0);
   expect(thrice.route).toBe('/viewer');
 });
+
+test('setEngineReady with missing engine is ignored', () => {
+  const state = createEmptyState();
+  produce(state, draft => {
+    StateActions.setEngineReady(
+        draft, {engineId: '1', ready: true, mode: 'WASM'});
+  });
+});
+
+test('setEngineReady', () => {
+  const state = createEmptyState();
+  state.nextId = 100;
+  const after = produce(state, draft => {
+    StateActions.openTraceFromUrl(draft, {
+      url: 'https://example.com/bar',
+    });
+    StateActions.setEngineReady(
+        draft, {engineId: '100', ready: true, mode: 'WASM'});
+  });
+  expect(after.engines['100'].ready).toBe(true);
+});
diff --git a/ui/src/common/aggregation_data.ts b/ui/src/common/aggregation_data.ts
index a666d92..8887857 100644
--- a/ui/src/common/aggregation_data.ts
+++ b/ui/src/common/aggregation_data.ts
@@ -40,6 +40,7 @@
 export interface ColumnDef {
   title: string;
   kind: string;
+  sum?: boolean;
   columnConstructor: TypedArrayConstructor;
   columnId: string;
 }
@@ -47,6 +48,16 @@
 export interface AggregateData {
   tabName: string;
   columns: Column[];
+  columnSums: string[];
   // For string interning.
   strings: string[];
+  // Some aggregations will have extra info to display;
+  extra?: ThreadStateExtra;
+}
+
+export interface ThreadStateExtra {
+  kind: 'THREAD_STATE';
+  states: string[];
+  values: Float64Array;
+  totalMs: number;
 }
\ No newline at end of file
diff --git a/ui/src/frontend/colorizer.ts b/ui/src/common/colorizer.ts
similarity index 84%
rename from ui/src/frontend/colorizer.ts
rename to ui/src/common/colorizer.ts
index 60d7038..79105cf 100644
--- a/ui/src/frontend/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -13,8 +13,6 @@
 // limitations under the License.
 
 import {hsl} from 'color-convert';
-import {translateState} from '../common/thread_state';
-import {ThreadDesc} from './globals';
 
 export interface Color {
   c: string;
@@ -65,6 +63,12 @@
   return (128 + (32 * cpu)) % 256;
 }
 
+const DESAT_RED: Color = {
+  c: 'desat red',
+  h: 3,
+  s: 30,
+  l: 49
+};
 const DARK_GREEN: Color = {
   c: 'dark green',
   h: 120,
@@ -97,13 +101,15 @@
   l: 48
 };
 
-export function colorForState(stateCode: string): Readonly<Color> {
-  const state = translateState(stateCode);
+export function colorForState(state: string): Readonly<Color> {
   if (state === 'Running') {
     return DARK_GREEN;
   } else if (state.startsWith('Runnable')) {
     return LIME_GREEN;
   } else if (state.includes('Uninterruptible Sleep')) {
+    if (state.includes('non-IO')) {
+      return DESAT_RED;
+    }
     return ORANGE;
   } else if (state.includes('Sleeping')) {
     return TRANSPARENT_WHITE;
@@ -111,12 +117,21 @@
   return INDIGO;
 }
 
+export function textColorForState(stateCode: string): string {
+  const background = colorForState(stateCode);
+  return background.l > 80 ? '#404040' : '#fff';
+}
+
 export function colorForTid(tid: number): Color {
   const colorIdx = hash(tid.toString(), MD_PALETTE.length);
   return Object.assign({}, MD_PALETTE[colorIdx]);
 }
 
-export function colorForThread(thread: ThreadDesc|undefined): Color {
+export function hueForSlice(sliceName: string): number {
+  return hash(sliceName, 360);
+}
+
+export function colorForThread(thread?: {pid?: number, tid: number}): Color {
   if (thread === undefined) {
     return Object.assign({}, GREY_COLOR);
   }
diff --git a/ui/src/frontend/colorizer_unittest.ts b/ui/src/common/colorizer_unittest.ts
similarity index 76%
rename from ui/src/frontend/colorizer_unittest.ts
rename to ui/src/common/colorizer_unittest.ts
index 265bb38..d3ed01e 100644
--- a/ui/src/frontend/colorizer_unittest.ts
+++ b/ui/src/common/colorizer_unittest.ts
@@ -13,42 +13,28 @@
 // limitations under the License.
 
 import {colorForThread, hueForCpu} from './colorizer';
-import {ThreadDesc} from './globals';
 
-const PROCESS_A_THREAD_A: ThreadDesc = {
-  utid: 1,
+const PROCESS_A_THREAD_A = {
   tid: 100,
-  threadName: 'threadA',
   pid: 100,
-  procName: 'procA',
 };
 
-const PROCESS_A_THREAD_B: ThreadDesc = {
-  utid: 2,
+const PROCESS_A_THREAD_B = {
   tid: 101,
-  threadName: 'threadB',
   pid: 100,
-  procName: 'procA',
 };
 
-const PROCESS_B_THREAD_A: ThreadDesc = {
-  utid: 3,
+const PROCESS_B_THREAD_A = {
   tid: 200,
-  threadName: 'threadA',
   pid: 200,
-  procName: 'procB',
 };
 
-const PROCESS_UNK_THREAD_A: ThreadDesc = {
-  utid: 4,
+const PROCESS_UNK_THREAD_A = {
   tid: 42,
-  threadName: 'unkThreadA',
 };
 
-const PROCESS_UNK_THREAD_B: ThreadDesc = {
-  utid: 5,
+const PROCESS_UNK_THREAD_B = {
   tid: 42,
-  threadName: 'unkThreadB',
 };
 
 test('it gives threads colors by pid if present', () => {
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 9e89581..c270010 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -18,6 +18,7 @@
   RawQueryArgs,
   RawQueryResult
 } from './protos';
+import {slowlyCountRows} from './query_iterator';
 import {TimeSpan} from './time';
 
 export interface LoadingTracker {
@@ -30,6 +31,8 @@
   endLoading(): void {}
 }
 
+export class QueryError extends Error {}
+
 /**
  * Abstract interface of a trace proccessor.
  * This is the TypeScript equivalent of src/trace_processor/rpc.h.
@@ -70,8 +73,8 @@
   abstract rawQuery(rawQueryArgs: Uint8Array): Promise<Uint8Array>;
 
   /*
-   * Performs computation of metrics and returns a proto-encoded TraceMetrics
-   * object.
+   * Performs computation of metrics and returns metric result and any errors.
+   * Metric result is a proto binary or text encoded TraceMetrics object.
    */
   abstract rawComputeMetric(computeMetricArgs: Uint8Array): Promise<Uint8Array>;
 
@@ -79,7 +82,17 @@
    * Shorthand for sending a SQL query to the engine.
    * Deals with {,un}marshalling of request/response args.
    */
-  async query(sqlQuery: string, userQuery = false): Promise<RawQueryResult> {
+  async query(sqlQuery: string): Promise<RawQueryResult> {
+    const result = await this.uncheckedQuery(sqlQuery);
+    if (result.error) {
+      throw new QueryError(`Query error "${sqlQuery}": ${result.error}`);
+    }
+    return result;
+  }
+
+  // This method is for noncritical queries that shouldn't throw an error
+  // on failure. The caller must handle the failure.
+  async uncheckedQuery(sqlQuery: string): Promise<RawQueryResult> {
     this.loadingTracker.beginLoading();
     try {
       const args = new RawQueryArgs();
@@ -88,9 +101,7 @@
       const argsEncoded = RawQueryArgs.encode(args).finish();
       const respEncoded = await this.rawQuery(argsEncoded);
       const result = RawQueryResult.decode(respEncoded);
-      if (!result.error || userQuery) return result;
-      // Query failed, throw an error since it was not a user query
-      throw new Error(`Query error "${sqlQuery}": ${result.error}`);
+      return result;
     } finally {
       this.loadingTracker.endLoading();
     }
@@ -103,15 +114,20 @@
   async computeMetric(metrics: string[]): Promise<ComputeMetricResult> {
     const args = new ComputeMetricArgs();
     args.metricNames = metrics;
+    args.format = ComputeMetricArgs.ResultFormat.TEXTPROTO;
     const argsEncoded = ComputeMetricArgs.encode(args).finish();
     const respEncoded = await this.rawComputeMetric(argsEncoded);
-    return ComputeMetricResult.decode(respEncoded);
+    const result = ComputeMetricResult.decode(respEncoded);
+    if (result.error.length > 0) {
+      throw new QueryError(result.error);
+    }
+    return result;
   }
 
   async queryOneRow(query: string): Promise<number[]> {
     const result = await this.query(query);
     const res: number[] = [];
-    if (result.numRecords === 0) return res;
+    if (slowlyCountRows(result) === 0) return res;
     for (const col of result.columns) {
       if (col.longValues!.length === 0) {
         console.error(
@@ -131,7 +147,7 @@
     if (!this._cpus) {
       const result =
           await this.query('select distinct(cpu) from sched order by cpu;');
-      if (result.numRecords === 0) return [];
+      if (slowlyCountRows(result) === 0) return [];
       this._cpus = result.columns[0].longValues!.map(n => +n);
     }
     return this._cpus;
diff --git a/ui/src/common/flamegraph_unittest.ts b/ui/src/common/flamegraph_unittest.ts
index dfb03a4..d25464c 100644
--- a/ui/src/common/flamegraph_unittest.ts
+++ b/ui/src/common/flamegraph_unittest.ts
@@ -25,7 +25,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -35,7 +36,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -45,7 +47,8 @@
       totalSize: 4,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -55,7 +58,8 @@
       totalSize: 4,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -75,7 +79,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -85,7 +90,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -95,7 +101,8 @@
       totalSize: 6,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -105,7 +112,8 @@
       totalSize: 4,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 5,
@@ -115,7 +123,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -135,7 +144,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -145,7 +155,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -155,7 +166,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -170,7 +182,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -180,7 +193,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
   ]);
 });
@@ -195,7 +209,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -205,7 +220,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -215,7 +231,8 @@
       totalSize: 3,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -225,7 +242,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 5,
@@ -235,7 +253,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 6,
@@ -245,7 +264,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 7,
@@ -255,7 +275,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 8,
@@ -265,7 +286,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -278,7 +300,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -288,7 +311,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -298,7 +322,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 6,
@@ -308,7 +333,8 @@
       totalSize: 3,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
   ];
 
@@ -330,7 +356,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -340,7 +367,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -350,7 +378,8 @@
       totalSize: 3,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -360,7 +389,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 5,
@@ -370,7 +400,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 6,
@@ -380,7 +411,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 7,
@@ -390,7 +422,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 8,
@@ -400,7 +433,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -413,7 +447,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -423,7 +458,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -433,7 +469,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 6,
@@ -443,7 +480,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 7,
@@ -453,7 +491,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 8,
@@ -463,7 +502,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -486,7 +526,8 @@
       totalSize: 20,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -496,7 +537,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -506,7 +548,8 @@
       totalSize: 1,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -516,7 +559,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 5,
@@ -526,7 +570,8 @@
       totalSize: 3,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -539,7 +584,8 @@
       totalSize: 20,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -549,7 +595,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -559,7 +606,8 @@
       totalSize: 4,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 4,
@@ -569,7 +617,8 @@
       totalSize: 8,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -592,7 +641,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -602,7 +652,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -612,7 +663,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -631,7 +683,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -641,7 +694,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -656,7 +710,8 @@
       totalSize: 12,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
   ]);
 });
@@ -671,7 +726,8 @@
       totalSize: 60,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -681,7 +737,8 @@
       totalSize: 40,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -691,7 +748,8 @@
       totalSize: 25,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -701,7 +759,8 @@
       totalSize: 15,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 5,
@@ -711,7 +770,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 6,
@@ -721,7 +781,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 7,
@@ -731,7 +792,8 @@
       totalSize: 30,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 8,
@@ -741,7 +803,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 9,
@@ -751,7 +814,8 @@
       totalSize: 20,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 10,
@@ -761,7 +825,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 11,
@@ -771,7 +836,8 @@
       totalSize: 3,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 12,
@@ -781,7 +847,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 13,
@@ -791,7 +858,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 14,
@@ -801,7 +869,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 15,
@@ -811,7 +880,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 16,
@@ -821,7 +891,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 17,
@@ -831,7 +902,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 18,
@@ -841,7 +913,8 @@
       totalSize: 5,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 19,
@@ -851,7 +924,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 20,
@@ -861,7 +935,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
@@ -874,7 +949,8 @@
       totalSize: 60,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 2,
@@ -884,7 +960,8 @@
       totalSize: 40,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 3,
@@ -894,7 +971,8 @@
       totalSize: 25,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 4,
@@ -904,7 +982,8 @@
       totalSize: 35,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 7,
@@ -914,7 +993,8 @@
       totalSize: 30,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 8,
@@ -924,7 +1004,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 9,
@@ -934,7 +1015,8 @@
       totalSize: 20,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 10,
@@ -944,7 +1026,8 @@
       totalSize: 25,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 15,
@@ -954,7 +1037,8 @@
       totalSize: 25,
       selfSize: 0,
       mapping: 'x',
-      merged: true
+      merged: true,
+      highlighted: false
     },
     {
       id: 19,
@@ -964,7 +1048,8 @@
       totalSize: 10,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
     {
       id: 20,
@@ -974,7 +1059,8 @@
       totalSize: 2,
       selfSize: 0,
       mapping: 'x',
-      merged: false
+      merged: false,
+      highlighted: false
     },
   ];
 
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index a94eddd..f083428 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -103,6 +103,7 @@
     mapping: callsite.mapping,
     selfSize: callsite.selfSize,
     merged: callsite.merged,
+    highlighted: callsite.highlighted
   };
 }
 
diff --git a/ui/src/common/http_rpc_engine.ts b/ui/src/common/http_rpc_engine.ts
index 38ed932..bd1f6dd 100644
--- a/ui/src/common/http_rpc_engine.ts
+++ b/ui/src/common/http_rpc_engine.ts
@@ -136,6 +136,10 @@
 
   static async checkConnection(): Promise<HttpRpcState> {
     const httpRpcState: HttpRpcState = {connected: false};
+    console.info(
+        `It's safe to ignore the ERR_CONNECTION_REFUSED on ${RPC_URL} below. ` +
+        `That might happen while probing the exernal native accelerator. The ` +
+        `error is non-fatal and unlikely to be the culprit for any UI bug.`);
     try {
       const resp = await fetchWithTimeout(
           RPC_URL + 'status',
diff --git a/ui/src/common/metric_data.ts b/ui/src/common/metric_data.ts
new file mode 100644
index 0000000..a9db6c8
--- /dev/null
+++ b/ui/src/common/metric_data.ts
@@ -0,0 +1,20 @@
+// 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.
+
+export interface MetricResult {
+  name: string;
+  // Either result or error should be set.
+  resultString?: string;
+  error?: string;
+}
diff --git a/ui/src/common/protos.ts b/ui/src/common/protos.ts
index 52160fd..8b0a5fd 100644
--- a/ui/src/common/protos.ts
+++ b/ui/src/common/protos.ts
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertFalse, assertTrue} from '../base/logging';
 import * as protos from '../gen/protos';
+import {slowlyCountRows} from './query_iterator';
 
 // Aliases protos to avoid the super nested namespaces.
 // See https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases
@@ -104,7 +104,7 @@
 export function* rawQueryResultIter(result: RawQueryResult) {
   const columns: Array<[string, number]> = rawQueryResultColumns(result).map(
       (name, i): [string, number] => [name, i]);
-  for (let rowNum = 0; rowNum < result.numRecords; rowNum++) {
+  for (let rowNum = 0; rowNum < slowlyCountRows(result); rowNum++) {
     const row: Row = {};
     for (const [name, colNum] of columns) {
       const cell = getCell(result, colNum, rowNum);
@@ -114,96 +114,6 @@
   }
 }
 
-export const NUM = 0;
-export const STR = 'str';
-export const NUM_NULL: number|null = 1;
-export const STR_NULL: string|null = 'str_null';
-
-/**
- * This function allows for type safe use of RawQueryResults.
- * The input is a RawQueryResult (|raw|) and a "spec".
- * A spec is an object where the keys are column names and the values
- * are constants representing the types. For example:
- * {
- *   upid: NUM,
- *   pid: NUM_NULL,
- *   processName: STR_NULL,
- * }
- * The output is a iterable of rows each row looks like the given spec:
- * {
- *   upid: 1,
- *   pid: 42,
- *   processName: null,
- * }
- * Each row has an appropriate typescript type based on the spec so there
- * is no need to use ! or cast when using the result of rawQueryToRows.
- * Note: type checking to ensure that the RawQueryResult matches the spec
- * happens at runtime, so if a query can return null and this is not reflected
- * in the spec this will still crash at runtime.
- */
-export function*
-    rawQueryToRows<T>(raw: RawQueryResult, spec: T): IterableIterator<T> {
-  const allColumns = rawQueryResultColumns(raw);
-  const columns: Array<[string, (row: number) => string | number | null]> = [];
-  for (const [key, columnSpec] of Object.entries(spec)) {
-    const i = allColumns.indexOf(key);
-    assertTrue(i !== -1, `Expected column "${key}" (cols ${allColumns})`);
-
-    const column = raw.columns[i];
-    const isNulls = column.isNulls!;
-    const columnType = raw.columnDescriptors[i].type;
-
-    if (columnSpec === NUM || columnSpec === STR) {
-      for (let j = 0; j < raw.numRecords; j++) {
-        assertFalse(column.isNulls![i], `Unexpected null in ${key} row ${j}`);
-      }
-    }
-
-    if (columnSpec === NUM || columnSpec === NUM_NULL) {
-      if (columnType === COLUMN_TYPE_STR) {
-        throw new Error(`Expected numbers in column ${key} found strings`);
-      }
-    } else if (columnSpec === STR || columnSpec === STR_NULL) {
-      if (columnType === COLUMN_TYPE_LONG ||
-          columnType === COLUMN_TYPE_DOUBLE) {
-        throw new Error(`Expected strings in column ${key} found numbers`);
-      }
-    }
-
-    let accessor;
-    switch (columnType) {
-      case COLUMN_TYPE_LONG: {
-        const values = column.longValues!;
-        accessor = (i: number) => isNulls[i] ? null : +values[i];
-        break;
-      }
-      case COLUMN_TYPE_DOUBLE: {
-        const values = column.doubleValues!;
-        accessor = (i: number) => isNulls[i] ? null : values[i];
-        break;
-      }
-      case COLUMN_TYPE_STR: {
-        const values = column.stringValues!;
-        accessor = (i: number) => isNulls[i] ? null : values[i];
-        break;
-      }
-      default:
-        // We can only reach here if the column is completely null.
-        accessor = (_: number) => null;
-        break;
-    }
-    columns.push([key, accessor]);
-  }
-
-  for (let i = 0; i < raw.numRecords; i++) {
-    const row: {[_: string]: number | string | null} = {};
-    for (const [name, accessor] of columns) {
-      row[name] = accessor(i);
-    }
-    yield row as {} as T;
-  }
-}
-
 export {
   AndroidLogConfig,
   AndroidLogId,
diff --git a/ui/src/common/query_iterator.ts b/ui/src/common/query_iterator.ts
index 1e41c41..ccea06f 100644
--- a/ui/src/common/query_iterator.ts
+++ b/ui/src/common/query_iterator.ts
@@ -52,6 +52,7 @@
   const disallowNulls = columnType === STR || columnType === NUM;
   const expectsStrings = columnType === STR || columnType === STR_NULL;
   const expectsNumbers = columnType === NUM || columnType === NUM_NULL;
+  const isEmpty = +result.numRecords === 0;
 
   for (let i = 0; i < result.columnDescriptors.length; ++i) {
     const descriptor = result.columnDescriptors[i];
@@ -68,16 +69,16 @@
       throw new Error(`Multiple columns with the name ${name}`);
     }
 
-    if (expectsStrings && (hasDoubles || hasLongs)) {
+    if (expectsStrings && !hasStrings && !isEmpty) {
       throw new Error(`Expected strings for column ${name} but found numbers`);
     }
 
-    if (expectsNumbers && hasStrings) {
+    if (expectsNumbers && !hasDoubles && !hasLongs && !isEmpty) {
       throw new Error(`Expected numbers for column ${name} but found strings`);
     }
 
     if (disallowNulls) {
-      for (let j = 0; j < result.numRecords; ++j) {
+      for (let j = 0; j < +result.numRecords; ++j) {
         if (column.isNulls![j] === true) {
           throw new Error(`Column ${name} contains nulls`);
         }
@@ -118,13 +119,18 @@
       this.columnCount_++;
       this.columnNames_.push(columnName);
       let values: string[]|Array<number|Long> = [];
-      if (column.longValues && column.longValues.length > 0) {
+      const isNum = columnType === NUM || columnType === NUM_NULL;
+      const isString = columnType === STR || columnType === STR_NULL;
+      if (isNum && column.longValues &&
+          column.longValues.length === this.rowCount_) {
         values = column.longValues;
       }
-      if (column.doubleValues && column.doubleValues.length > 0) {
+      if (isNum && column.doubleValues &&
+          column.doubleValues.length === this.rowCount_) {
         values = column.doubleValues;
       }
-      if (column.stringValues && column.stringValues.length > 0) {
+      if (isString && column.stringValues &&
+          column.stringValues.length === this.rowCount_) {
         values = column.stringValues;
       }
       this.columns_.push(values as string[]);
diff --git a/ui/src/common/query_iterator_unittest.ts b/ui/src/common/query_iterator_unittest.ts
index d6eca47..402a2e9 100644
--- a/ui/src/common/query_iterator_unittest.ts
+++ b/ui/src/common/query_iterator_unittest.ts
@@ -171,3 +171,65 @@
 
   expect(it.valid()).toBe(false);
 });
+
+test('Columnar iteration over empty query set', () => {
+  const r = new RawQueryResult({
+    columnDescriptors: [{
+      name: 'emptyColumn',
+      type: COLUMN_TYPE_STR,
+    }],
+    numRecords: 0,
+    columns: [{
+      stringValues: [],
+      isNulls: [],
+    }],
+  });
+
+  {
+    const it = iter({'emptyColumn': STR}, r);
+    expect(it.valid()).toBe(false);
+  }
+
+  {
+    const it = iter({'emptyColumn': NUM}, r);
+    expect(it.valid()).toBe(false);
+  }
+
+  {
+    const it = iter({'emptyColumn': NUM_NULL}, r);
+    expect(it.valid()).toBe(false);
+  }
+
+  {
+    const it = iter({'emptyColumn': STR_NULL}, r);
+    expect(it.valid()).toBe(false);
+  }
+});
+
+test('Columnar iteration first row is null', () => {
+  const r = new RawQueryResult({
+    columnDescriptors: [{
+      name: 'numbers',
+      type: COLUMN_TYPE_STR,
+    }],
+    numRecords: 2,
+    columns: [{
+      stringValues: ['[NULL]'],
+      doubleValues: [0],
+      longValues: [0, 1],
+      isNulls: [true, false],
+    }],
+  });
+
+  const it = iter({'numbers': NUM_NULL}, r);
+
+  expect(it.valid()).toBe(true);
+  expect(it.row.numbers).toBe(null);
+  it.next();
+
+  expect(it.valid()).toBe(true);
+  expect(it.row.numbers).toBe(1);
+  it.next();
+
+  expect(it.valid()).toBe(false);
+});
diff --git a/ui/src/common/search_data.ts b/ui/src/common/search_data.ts
index 0969c1d..70bfb7f 100644
--- a/ui/src/common/search_data.ts
+++ b/ui/src/common/search_data.ts
@@ -19,9 +19,9 @@
 }
 
 export interface CurrentSearchResults {
-  sliceIds: Float64Array;
-  tsStarts: Float64Array;
-  utids: Float64Array;
+  sliceIds: number[];
+  tsStarts: number[];
+  utids: number[];
   trackIds: string[];
   sources: string[];
   totalResults: number;
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 028fde9..472edde 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -29,10 +29,20 @@
 export type VisibleState =
     Timestamped<{startSec: number; endSec: number; resolution: number;}>;
 
-export type TimestampedAreaSelection = Timestamped<AreaSelection>;
 export interface AreaSelection {
-  area?: Area;
+  kind: 'AREA';
+  areaId: string;
+  // When an area is marked it will be assigned a unique note id and saved as
+  // an AreaNote for the user to return to later. id = 0 is the special id that
+  // is overwritten when a new area is marked. Any other id is a persistent
+  // marking that will not be overwritten.
+  // When not set, the area selection will be replaced with any
+  // new area selection (i.e. not saved anywhere).
+  noteId?: string;
 }
+
+export type AreaById = Area&{id: string};
+
 export interface Area {
   startSec: number;
   endSec: number;
@@ -41,8 +51,9 @@
 
 export const MAX_TIME = 180;
 
-export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
+export const STATE_VERSION = 2;
 
+export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
 export type EngineMode = 'WASM'|'HTTP_RPC';
 
@@ -60,6 +71,7 @@
   selfSize: number;
   mapping: string;
   merged: boolean;
+  highlighted: boolean;
 }
 
 export interface TraceFileSource {
@@ -91,6 +103,7 @@
   engineId: string;
   kind: string;
   name: string;
+  isMainThread: boolean;
   trackGroup?: string;
   config: {};
 }
@@ -101,7 +114,6 @@
   name: string;
   collapsed: boolean;
   tracks: string[];  // Child track ids.
-  summaryTrackId: string;
 }
 
 export interface EngineConfig {
@@ -121,6 +133,8 @@
 export interface PermalinkConfig {
   requestId?: string;  // Set by the frontend to request a new permalink.
   hash?: string;       // Set by the controller when the link has been created.
+  isRecordingConfig?:
+      boolean;  // this permalink request is for a recording config only
 }
 
 export interface TraceTime {
@@ -131,7 +145,6 @@
 export interface FrontendLocalState {
   omniboxState: OmniboxState;
   visibleState: VisibleState;
-  selectedArea: TimestampedAreaSelection;
 }
 
 export interface Status {
@@ -150,8 +163,7 @@
 export interface AreaNote {
   noteType: 'AREA';
   id: string;
-  timestamp: number;
-  area: Area;
+  areaId: string;
   color: string;
   text: string;
 }
@@ -207,16 +219,13 @@
 
 export interface ThreadStateSelection {
   kind: 'THREAD_STATE';
-  utid: number;
-  ts: number;
-  dur: number;
-  state: string;
-  cpu: number;
+  id: number;
 }
 
-type Selection = (NoteSelection|SliceSelection|CounterSelection|
-                  HeapProfileSelection|CpuProfileSampleSelection|
-                  ChromeSliceSelection|ThreadStateSelection)&{trackId?: string};
+type Selection =
+    (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection|
+     CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection|
+     AreaSelection)&{trackId?: string};
 
 export interface LogsPagination {
   offset: number;
@@ -242,11 +251,20 @@
   sorting?: Sorting;
 }
 
+export interface MetricsState {
+  availableMetrics?: string[];  // Undefined until list is loaded.
+  selectedIndex?: number;
+  requestedMetric?: string;  // Unset after metric request is handled.
+}
+
 export interface State {
   // tslint:disable-next-line:no-any
   [key: string]: any;
+  version: number;
   route: string|null;
   nextId: number;
+  nextNoteId: number;
+  nextAreaId: number;
 
   /**
    * State of the ConfigEditor.
@@ -262,11 +280,15 @@
   traceTime: TraceTime;
   trackGroups: ObjectById<TrackGroupState>;
   tracks: ObjectById<TrackState>;
+  areas: ObjectById<AreaById>;
   aggregatePreferences: ObjectById<AggregationState>;
   visibleTracks: string[];
   scrollingTracks: string[];
   pinnedTracks: string[];
+  debugTrackId?: string;
+  lastTrackReloadRequest?: number;
   queries: ObjectById<QueryConfig>;
+  metrics: MetricsState;
   permalink: PermalinkConfig;
   notes: ObjectById<Note|AreaNote>;
   status: Status;
@@ -300,6 +322,7 @@
   lastRecordingError?: string;
   recordingStatus?: string;
 
+  updateChromeCategories: boolean;
   chromeCategories: string[]|undefined;
   analyzePageQuery?: string;
 }
@@ -313,7 +336,7 @@
     'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE';
 
 // 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome.
-export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L';
+export declare type TargetOs = 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS';
 
 export function isAndroidP(target: RecordingTarget) {
   return target.os === 'P';
@@ -324,7 +347,11 @@
 }
 
 export function isChromeTarget(target: RecordingTarget) {
-  return target.os === 'C';
+  return ['C', 'CrOS'].includes(target.os);
+}
+
+export function isCrOSTarget(target: RecordingTarget) {
+  return target.os === 'CrOS';
 }
 
 export function isLinuxTarget(target: RecordingTarget) {
@@ -348,7 +375,6 @@
   fileWritePeriodMs: number;  // Only for mode == 'LONG_TRACE'.
 
   cpuSched: boolean;
-  cpuLatency: boolean;
   cpuFreq: boolean;
   cpuCoarse: boolean;
   cpuCoarsePollMs: number;
@@ -357,6 +383,7 @@
   screenRecord: boolean;
 
   gpuFreq: boolean;
+  gpuMemTotal: boolean;
 
   ftrace: boolean;
   atrace: boolean;
@@ -407,16 +434,16 @@
     durationMs: 10000.0,
     maxFileSizeMb: 100,
     fileWritePeriodMs: 2500,
-    bufferSizeMb: 10.0,
+    bufferSizeMb: 64.0,
 
     cpuSched: false,
-    cpuLatency: false,
     cpuFreq: false,
     cpuSyscall: false,
 
     screenRecord: false,
 
     gpuFreq: false,
+    gpuMemTotal: false,
 
     ftrace: false,
     atrace: false,
@@ -472,6 +499,7 @@
     {os: 'P', name: 'Android P'},
     {os: 'O', name: 'Android O-'},
     {os: 'C', name: 'Chrome'},
+    {os: 'CrOS', name: 'Chrome OS (system trace)'},
     {os: 'L', name: 'Linux desktop'}
   ];
 }
@@ -686,8 +714,11 @@
 
 export function createEmptyState(): State {
   return {
+    version: STATE_VERSION,
     route: null,
     nextId: 0,
+    nextNoteId: 1,  // 0 is reserved for ephemeral area marking.
+    nextAreaId: 0,
     newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE',
     engines: {},
     traceTime: {...defaultTraceTime},
@@ -697,7 +728,9 @@
     visibleTracks: [],
     pinnedTracks: [],
     scrollingTracks: [],
+    areas: {},
     queries: {},
+    metrics: {},
     permalink: {},
     notes: {},
 
@@ -716,9 +749,6 @@
         lastUpdate: 0,
         resolution: 0,
       },
-      selectedArea: {
-        lastUpdate: 0,
-      }
     },
 
     logsPagination: {
@@ -742,6 +772,7 @@
     recordingTarget: getDefaultRecordingTargets()[0],
     availableAdbDevices: [],
 
+    updateChromeCategories: false,
     chromeCategories: undefined,
   };
 }
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 5216392..60d6835 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -26,6 +26,7 @@
     engineId: 'engine',
     kind: 'Foo',
     name: 'a track',
+    isMainThread: false,
     config: {},
   };
 
@@ -34,6 +35,7 @@
     engineId: 'engine',
     kind: 'Foo',
     name: 'b track',
+    isMainThread: false,
     config: {},
     trackGroup: 'containsB',
   };
diff --git a/ui/src/common/thread_state.ts b/ui/src/common/thread_state.ts
index 91de9ae..c0d68f5 100644
--- a/ui/src/common/thread_state.ts
+++ b/ui/src/common/thread_state.ts
@@ -29,12 +29,18 @@
   '+': '(Preempted)'
 };
 
-export function translateState(state: string|undefined) {
+export function translateState(
+    state: string|undefined, ioWait: boolean|undefined = undefined) {
   if (state === undefined) return '';
-  if (state === 'Running' || state === 'Various states') {
+  if (state === 'Running') {
     return state;
   }
   let result = states[state[0]];
+  if (ioWait === true) {
+    result += ' (IO)';
+  } else if (ioWait === false) {
+    result += ' (non-IO)';
+  }
   for (let i = 1; i < state.length; i++) {
     result += state[i] === '+' ? ' ' : ' + ';
     result += states[state[i]];
diff --git a/ui/src/common/upload_utils.ts b/ui/src/common/upload_utils.ts
new file mode 100644
index 0000000..03a9e39
--- /dev/null
+++ b/ui/src/common/upload_utils.ts
@@ -0,0 +1,59 @@
+// 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.
+
+export const BUCKET_NAME = 'perfetto-ui-data';
+import * as uuidv4 from 'uuid/v4';
+import {State, RecordConfig} from './state';
+
+export async function saveTrace(trace: File|ArrayBuffer): Promise<string> {
+  // TODO(hjd): This should probably also be a hash but that requires
+  // trace processor support.
+  const name = uuidv4();
+  const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
+      `${BUCKET_NAME}/o?uploadType=media` +
+      `&name=${name}&predefinedAcl=publicRead`;
+  const response = await fetch(url, {
+    method: 'post',
+    headers: {'Content-Type': 'application/octet-stream;'},
+    body: trace,
+  });
+  await response.json();
+  return `https://storage.googleapis.com/${BUCKET_NAME}/${name}`;
+}
+
+export async function saveState(stateOrConfig: State|
+                                RecordConfig): Promise<string> {
+  const text = JSON.stringify(stateOrConfig);
+  const hash = await toSha256(text);
+  const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
+      `${BUCKET_NAME}/o?uploadType=media` +
+      `&name=${hash}&predefinedAcl=publicRead`;
+  const response = await fetch(url, {
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/json; charset=utf-8',
+    },
+    body: text,
+  });
+  await response.json();
+  return hash;
+}
+
+export async function toSha256(str: string): Promise<string> {
+  // TODO(hjd): TypeScript bug with definition of TextEncoder.
+  // tslint:disable-next-line no-any
+  const buffer = new (TextEncoder as any)('utf-8').encode(str);
+  const digest = await crypto.subtle.digest('SHA-256', buffer);
+  return Array.from(new Uint8Array(digest)).map(x => x.toString(16)).join('');
+}
\ No newline at end of file
diff --git a/ui/src/common/wasm_engine_proxy.ts b/ui/src/common/wasm_engine_proxy.ts
index 3377777..43f7b85 100644
--- a/ui/src/common/wasm_engine_proxy.ts
+++ b/ui/src/common/wasm_engine_proxy.ts
@@ -146,10 +146,11 @@
     // Requests should be executed and ACKed by the worker in the same order
     // they came in.
     assertTrue(request.id === response.id);
-    if (response.aborted) {
-      request.respHandler.reject('WASM module crashed');
-    } else {
-      request.respHandler.resolve(response.data);
-    }
+
+    // If the Wasm call fails (e.g. hits a PERFETTO_CHECK) it will throw an
+    // error in wasm_bridge.ts and show the crash dialog. In no case we can
+    // gracefully handle a Wasm crash, so we fail fast there rather than
+    // propagating the error here rejecting the promise.
+    request.respHandler.resolve(response.data);
   }
 }
diff --git a/ui/src/controller/adb_record_controller_jsdomtest.ts b/ui/src/controller/adb_record_controller_jsdomtest.ts
index 204033c..23730bd 100644
--- a/ui/src/controller/adb_record_controller_jsdomtest.ts
+++ b/ui/src/controller/adb_record_controller_jsdomtest.ts
@@ -14,7 +14,7 @@
 
 import {dingus} from 'dingusjs';
 
-import {stringToUint8Array} from '../base/string_utils';
+import {utf8Encode} from '../base/string_utils';
 import {perfetto} from '../gen/protos';
 
 import {AdbStream, MockAdb, MockAdbStream} from './adb_interfaces';
@@ -93,7 +93,7 @@
   expect(sendMessage).toHaveBeenCalledTimes(0);
 
 
-  stream.onData(stringToUint8Array('starting tracing Wrote 123 bytes'));
+  stream.onData(utf8Encode('starting tracing Wrote 123 bytes'));
   stream.onClose();
 
   expect(adbController.sendErrorMessage).toHaveBeenCalledTimes(0);
diff --git a/ui/src/controller/adb_shell_controller.ts b/ui/src/controller/adb_shell_controller.ts
index 62c8204..e6059e8 100644
--- a/ui/src/controller/adb_shell_controller.ts
+++ b/ui/src/controller/adb_shell_controller.ts
@@ -15,7 +15,7 @@
 import {_TextDecoder} from 'custom_utils';
 
 import {extractTraceConfig} from '../base/extract_utils';
-import {uint8ArrayToBase64} from '../base/string_utils';
+import {base64Encode} from '../base/string_utils';
 
 import {AdbAuthState, AdbBaseConsumerPort} from './adb_base_controller';
 import {Adb, AdbStream} from './adb_interfaces';
@@ -181,7 +181,7 @@
   }
 
   generateStartTracingCommand(tracingConfig: Uint8Array) {
-    const configBase64 = uint8ArrayToBase64(tracingConfig);
+    const configBase64 = base64Encode(tracingConfig);
     const perfettoCmd = `perfetto -c - -o ${this.traceDestFile}`;
     return `echo '${configBase64}' | base64 -d | ${perfettoCmd}`;
   }
diff --git a/ui/src/controller/adb_socket_controller.ts b/ui/src/controller/adb_socket_controller.ts
index 7dd7b1d..f517f7a 100644
--- a/ui/src/controller/adb_socket_controller.ts
+++ b/ui/src/controller/adb_socket_controller.ts
@@ -161,7 +161,11 @@
   }
 
   private parseMessage(frameBuffer: Uint8Array) {
-    const frame = perfetto.protos.IPCFrame.decode(frameBuffer);
+    // Copy message to new array:
+    const buf = new ArrayBuffer(frameBuffer.byteLength);
+    const arr = new Uint8Array(buf);
+    arr.set(frameBuffer);
+    const frame = perfetto.protos.IPCFrame.decode(arr);
     this.handleIncomingFrame(frame);
   }
 
diff --git a/ui/src/controller/aggregation/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
index 4f6d4e5..7b2d941 100644
--- a/ui/src/controller/aggregation/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -12,9 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {AggregateData, Column, ColumnDef} from '../../common/aggregation_data';
+import {
+  AggregateData,
+  Column,
+  ColumnDef,
+  ThreadStateExtra,
+} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {Sorting, TimestampedAreaSelection} from '../../common/state';
+import {slowlyCountRows} from '../../common/query_iterator';
+import {Area, Sorting} from '../../common/state';
 import {Controller} from '../controller';
 import {globals} from '../globals';
 
@@ -25,13 +31,16 @@
 
 export abstract class AggregationController extends Controller<'main'> {
   readonly kind: string;
-  private previousArea: TimestampedAreaSelection = {lastUpdate: 0};
+  private previousArea?: Area;
   private previousSorting?: Sorting;
   private requestingData = false;
   private queuedRequest = false;
 
-  abstract async createAggregateView(
-      engine: Engine, area: TimestampedAreaSelection): Promise<boolean>;
+  abstract async createAggregateView(engine: Engine, area: Area):
+      Promise<boolean>;
+
+  abstract async getExtra(engine: Engine, area: Area):
+      Promise<ThreadStateExtra|void>;
 
   abstract getTabName(): string;
   abstract getDefaultSorting(): Sorting;
@@ -43,11 +52,24 @@
   }
 
   run() {
-    const selectedArea = globals.state.frontendLocalState.selectedArea;
+    const selection = globals.state.currentSelection;
+    if (selection === null || selection.kind !== 'AREA') {
+      globals.publish('AggregateData', {
+        data: {
+          tabName: this.getTabName(),
+          columns: [],
+          strings: [],
+          columnSums: [],
+        },
+        kind: this.args.kind
+      });
+      return;
+    }
+    const selectedArea = globals.state.areas[selection.areaId];
     const aggregatePreferences =
         globals.state.aggregatePreferences[this.args.kind];
 
-    const areaChanged = this.previousArea.lastUpdate < selectedArea.lastUpdate;
+    const areaChanged = this.previousArea !== selectedArea;
     const sortingChanged = aggregatePreferences &&
         this.previousSorting !== aggregatePreferences.sorting;
     if (!areaChanged && !sortingChanged) return;
@@ -57,8 +79,8 @@
     } else {
       this.requestingData = true;
       if (sortingChanged) this.previousSorting = aggregatePreferences.sorting;
-      if (areaChanged) this.previousArea = selectedArea;
-      this.getAggregateData(areaChanged)
+      if (areaChanged) this.previousArea = Object.assign({}, selectedArea);
+      this.getAggregateData(selectedArea, areaChanged)
           .then(
               data => globals.publish(
                   'AggregateData', {data, kind: this.args.kind}))
@@ -72,13 +94,17 @@
     }
   }
 
-  async getAggregateData(areaChanged: boolean): Promise<AggregateData> {
-    const selectedArea = globals.state.frontendLocalState.selectedArea;
+  async getAggregateData(area: Area, areaChanged: boolean):
+      Promise<AggregateData> {
     if (areaChanged) {
-      const viewExists =
-          await this.createAggregateView(this.args.engine, selectedArea);
+      const viewExists = await this.createAggregateView(this.args.engine, area);
       if (!viewExists) {
-        return {tabName: this.getTabName(), columns: [], strings: []};
+        return {
+          tabName: this.getTabName(),
+          columns: [],
+          strings: [],
+          columnSums: [],
+        };
       }
     }
 
@@ -93,11 +119,13 @@
     const query = `select ${colIds} from ${this.kind} order by ${sorting}`;
     const result = await this.args.engine.query(query);
 
-    const numRows = +result.numRecords;
+    const numRows = slowlyCountRows(result);
     const columns = defs.map(def => this.columnFromColumnDef(def, numRows));
-
-    const data:
-        AggregateData = {tabName: this.getTabName(), columns, strings: []};
+    const columnSums = await Promise.all(defs.map(def => this.getSum(def)));
+    const extraData = await this.getExtra(this.args.engine, area);
+    const extra = extraData ? extraData : undefined;
+    const data: AggregateData =
+        {tabName: this.getTabName(), columns, columnSums, strings: [], extra};
 
     const stringIndexes = new Map<string, number>();
     function internString(str: string) {
@@ -126,6 +154,17 @@
     return data;
   }
 
+  async getSum(def: ColumnDef): Promise<string> {
+    if (!def.sum) return '';
+    const result = await this.args.engine.queryOneRow(
+        `select sum(${def.columnId}) from ${this.kind}`);
+    let sum = result[0];
+    if (def.kind === 'TIMESTAMP_NS') {
+      sum = sum / 1e6;
+    }
+    return `${sum}`;
+  }
+
   columnFromColumnDef(def: ColumnDef, numRows: number): Column {
     // TODO(taylori): The Column type should be based on the
     // ColumnDef type or vice versa to avoid this cast.
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index 52536f0..ff2dbc3 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -14,7 +14,7 @@
 
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {Sorting, TimestampedAreaSelection} from '../../common/state';
+import {Area, Sorting} from '../../common/state';
 import {toNs} from '../../common/time';
 import {Config, COUNTER_TRACK_KIND} from '../../tracks/counter/common';
 import {globals} from '../globals';
@@ -22,11 +22,8 @@
 import {AggregationController} from './aggregation_controller';
 
 export class CounterAggregationController extends AggregationController {
-  async createAggregateView(
-      engine: Engine, selectedArea: TimestampedAreaSelection) {
+  async createAggregateView(engine: Engine, area: Area) {
     await engine.query(`drop view if exists ${this.kind};`);
-    const area = selectedArea.area;
-    if (area === undefined) return false;
 
     const ids = [];
     for (const trackId of area.tracks) {
@@ -104,7 +101,8 @@
         title: 'Count',
         kind: 'Number',
         columnConstructor: Float64Array,
-        columnId: 'count'
+        columnId: 'count',
+        sum: true,
       },
       {
         title: 'First value',
@@ -134,6 +132,8 @@
     ];
   }
 
+  async getExtra() {}
+
   getTabName() {
     return 'Counters';
   }
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index fcbc57a..70a643b 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -14,7 +14,7 @@
 
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {Sorting, TimestampedAreaSelection} from '../../common/state';
+import {Area, Sorting} from '../../common/state';
 import {toNs} from '../../common/time';
 import {Config, CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices/common';
 import {globals} from '../globals';
@@ -23,11 +23,8 @@
 
 
 export class CpuAggregationController extends AggregationController {
-  async createAggregateView(
-      engine: Engine, selectedArea: TimestampedAreaSelection) {
+  async createAggregateView(engine: Engine, area: Area) {
     await engine.query(`drop view if exists ${this.kind};`);
-    const area = selectedArea.area;
-    if (area === undefined) return false;
 
     const selectedCpus = [];
     for (const trackId of area.tracks) {
@@ -60,6 +57,8 @@
     return 'CPU Slices';
   }
 
+  async getExtra() {}
+
   getDefaultSorting(): Sorting {
     return {column: 'total_dur', direction: 'DESC'};
   }
@@ -94,7 +93,8 @@
         title: 'Wall duration (ms)',
         kind: 'TIMESTAMP_NS',
         columnConstructor: Float64Array,
-        columnId: 'total_dur'
+        columnId: 'total_dur',
+        sum: true
       },
       {
         title: 'Avg Wall duration (ms)',
@@ -106,7 +106,8 @@
         title: 'Occurrences',
         kind: 'NUMBER',
         columnConstructor: Uint16Array,
-        columnId: 'occurrences'
+        columnId: 'occurrences',
+        sum: true
       }
     ];
   }
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
new file mode 100644
index 0000000..f3b2807
--- /dev/null
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -0,0 +1,108 @@
+// 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.
+
+import {ColumnDef} from '../../common/aggregation_data';
+import {Engine} from '../../common/engine';
+import {Area, Sorting} from '../../common/state';
+import {toNs} from '../../common/time';
+import {
+  ASYNC_SLICE_TRACK_KIND,
+  Config as AsyncSliceConfig
+} from '../../tracks/async_slices/common';
+import {
+  Config as SliceConfig,
+  SLICE_TRACK_KIND
+} from '../../tracks/chrome_slices/common';
+import {globals} from '../globals';
+
+import {AggregationController} from './aggregation_controller';
+
+export class SliceAggregationController extends AggregationController {
+  async createAggregateView(engine: Engine, area: Area) {
+    await engine.query(`drop view if exists ${this.kind};`);
+
+    const selectedTrackIds = [];
+    for (const trackId of area.tracks) {
+      const track = globals.state.tracks[trackId];
+      // Track will be undefined for track groups.
+      if (track !== undefined) {
+        if (track.kind === SLICE_TRACK_KIND) {
+          selectedTrackIds.push((track.config as SliceConfig).trackId);
+        }
+        if (track.kind === ASYNC_SLICE_TRACK_KIND) {
+          const config = track.config as AsyncSliceConfig;
+          for (const id of config.trackIds) {
+            selectedTrackIds.push(id);
+          }
+        }
+      }
+    }
+    if (selectedTrackIds.length === 0) return false;
+
+    const query = `create view ${this.kind} as
+        SELECT
+        name,
+        sum(dur) AS total_dur,
+        sum(dur)/count(1) as avg_dur,
+        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`;
+
+    await engine.query(query);
+    return true;
+  }
+
+  getTabName() {
+    return 'Slices';
+  }
+
+  async getExtra() {}
+
+  getDefaultSorting(): Sorting {
+    return {column: 'total_dur', direction: 'DESC'};
+  }
+
+  getColumnDefinitions(): ColumnDef[] {
+    return [
+      {
+        title: 'Name',
+        kind: 'STRING',
+        columnConstructor: Uint16Array,
+        columnId: 'name',
+      },
+      {
+        title: 'Wall duration (ms)',
+        kind: 'TIMESTAMP_NS',
+        columnConstructor: Float64Array,
+        columnId: 'total_dur',
+        sum: true
+      },
+      {
+        title: 'Avg Wall duration (ms)',
+        kind: 'TIMESTAMP_NS',
+        columnConstructor: Float64Array,
+        columnId: 'avg_dur'
+      },
+      {
+        title: 'Occurrences',
+        kind: 'NUMBER',
+        columnConstructor: Uint16Array,
+        columnId: 'occurrences',
+        sum: true
+      }
+    ];
+  }
+}
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index b781275..03eb935 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {ColumnDef} from '../../common/aggregation_data';
+import {ColumnDef, ThreadStateExtra} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
-import {Sorting, TimestampedAreaSelection} from '../../common/state';
+import {slowlyCountRows} from '../../common/query_iterator';
+import {Area, Sorting} from '../../common/state';
+import {translateState} from '../../common/thread_state';
 import {toNs} from '../../common/time';
 import {
   Config,
@@ -25,42 +27,80 @@
 import {AggregationController} from './aggregation_controller';
 
 export class ThreadAggregationController extends AggregationController {
-  async createAggregateView(
-      engine: Engine, selectedArea: TimestampedAreaSelection) {
-    await engine.query(`drop view if exists ${this.kind};`);
-    const area = selectedArea.area;
-    if (area === undefined) return false;
+  private utids?: number[];
 
-    // TODO(taylori): Thread state tracks should have a real track id in the
-    // trace processor.
-    const utids = [];
-    for (const trackId of area.tracks) {
+  setThreadStateUtids(tracks: string[]) {
+    this.utids = [];
+    for (const trackId of tracks) {
       const track = globals.state.tracks[trackId];
       // Track will be undefined for track groups.
       if (track !== undefined && track.kind === THREAD_STATE_TRACK_KIND) {
-        utids.push((track.config as Config).utid);
+        this.utids.push((track.config as Config).utid);
       }
     }
-    if (utids.length === 0) return false;
+  }
 
-    const query = `create view ${this.kind} as
-      SELECT process.name as process_name, pid, thread.name as thread_name, tid,
-      state,
-      sum(dur) AS total_dur,
-      sum(dur)/count(1) as avg_dur,
-      count(1) as occurrences
+  async createAggregateView(engine: Engine, area: Area) {
+    await engine.query(`drop view if exists ${this.kind};`);
+    this.setThreadStateUtids(area.tracks);
+    if (this.utids === undefined || this.utids.length === 0) return false;
+
+    const query = `
+      create view ${this.kind} as
+      SELECT
+        process.name as process_name,
+        pid,
+        thread.name as thread_name,
+        tid,
+        state || ',' || IFNULL(io_wait, 'NULL') as concat_state,
+        sum(dur) AS total_dur,
+        sum(dur)/count(1) as avg_dur,
+        count(1) as occurrences
       FROM process
       JOIN thread USING(upid)
       JOIN thread_state USING(utid)
-      WHERE utid IN (${utids}) AND
+      WHERE utid IN (${this.utids}) AND
       thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
       thread_state.ts < ${toNs(area.endSec)}
-      GROUP BY utid, state`;
+      GROUP BY utid, concat_state
+    `;
 
     await engine.query(query);
     return true;
   }
 
+  async getExtra(engine: Engine, area: Area): Promise<ThreadStateExtra|void> {
+    this.setThreadStateUtids(area.tracks);
+    if (this.utids === undefined || this.utids.length === 0) return;
+
+    const query = `select state, io_wait, sum(dur) as total_dur from process
+      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)}
+      GROUP BY state, io_wait`;
+    const result = await engine.query(query);
+    const numRows = slowlyCountRows(result);
+
+    const summary: ThreadStateExtra = {
+      kind: 'THREAD_STATE',
+      states: [],
+      values: new Float64Array(numRows),
+      totalMs: 0
+    };
+    for (let row = 0; row < numRows; row++) {
+      const state = result.columns[0].stringValues![row];
+      const ioWait = result.columns[1].isNulls![row] ?
+          undefined :
+          !!result.columns[1].longValues![row];
+      summary.states.push(translateState(state, ioWait));
+      summary.values[row] = result.columns[2].longValues![row] / 1000000;  // ms
+    }
+    summary.totalMs = summary.values.reduce((a, b) => a + b, 0);
+    return summary;
+  }
+
   getColumnDefinitions(): ColumnDef[] {
     return [
       {
@@ -91,13 +131,14 @@
         title: 'State',
         kind: 'STATE',
         columnConstructor: Uint16Array,
-        columnId: 'state'
+        columnId: 'concat_state'
       },
       {
         title: 'Wall duration (ms)',
         kind: 'TIMESTAMP_NS',
         columnConstructor: Float64Array,
-        columnId: 'total_dur'
+        columnId: 'total_dur',
+        sum: true
       },
       {
         title: 'Avg Wall duration (ms)',
@@ -109,7 +150,8 @@
         title: 'Occurrences',
         kind: 'NUMBER',
         columnConstructor: Uint16Array,
-        columnId: 'occurrences'
+        columnId: 'occurrences',
+        sum: true
       }
     ];
   }
diff --git a/ui/src/controller/chrome_proxy_record_controller.ts b/ui/src/controller/chrome_proxy_record_controller.ts
index 54a4f29..8defb2b 100644
--- a/ui/src/controller/chrome_proxy_record_controller.ts
+++ b/ui/src/controller/chrome_proxy_record_controller.ts
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {stringToUint8Array, uint8ArrayToString} from '../base/string_utils';
+import {binaryDecode, binaryEncode} from '../base/string_utils';
+import {Actions} from '../common/actions';
 
 import {
   ConsumerPortResponse,
-  isConsumerPortResponse,
   isReadBuffersResponse,
   Typed
 } from './consumer_port_types';
+import {globals} from './globals';
 import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
 
 export interface ChromeExtensionError extends Typed {
@@ -30,8 +31,12 @@
   status: string;
 }
 
-export type ChromeExtensionMessage =
-    ChromeExtensionError|ChromeExtensionStatus|ConsumerPortResponse;
+export interface GetCategoriesResponse extends Typed {
+  categories: string[];
+}
+
+export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus|
+    ConsumerPortResponse|GetCategoriesResponse;
 
 function isError(obj: Typed): obj is ChromeExtensionError {
   return obj.type === 'ChromeExtensionError';
@@ -41,10 +46,20 @@
   return obj.type === 'ChromeExtensionStatus';
 }
 
+function isGetCategoriesResponse(obj: Typed): obj is GetCategoriesResponse {
+  return obj.type === 'GetCategoriesResponse';
+}
+
 // This class acts as a proxy from the record controller (running in a worker),
 // to the frontend. This is needed because we can't directly talk with the
 // extension from a web-worker, so we use a MessagePort to communicate with the
 // frontend, that will consecutively forward it to the extension.
+
+// Rationale for the binaryEncode / binaryDecode calls below:
+// Messages to/from extensions need to be JSON serializable. ArrayBuffers are
+// not supported. For this reason here we use binaryEncode/Decode.
+// See https://developer.chrome.com/extensions/messaging#simple
+
 export class ChromeExtensionConsumerPort extends RpcConsumerPort {
   private extensionPort: MessagePort;
 
@@ -63,24 +78,21 @@
       this.sendStatus(message.data.status);
       return;
     }
+    if (isGetCategoriesResponse(message.data)) {
+      globals.dispatch(Actions.setChromeCategories(message.data));
+      return;
+    }
 
-    console.assert(isConsumerPortResponse(message.data));
     // In this else branch message.data will be a ConsumerPortResponse.
     if (isReadBuffersResponse(message.data) && message.data.slices) {
-      // This is needed because we can't send an ArrayBuffer through a
-      // chrome.runtime.port. A string is sent instead, and here converted to
-      // an ArrayBuffer.
       const slice = message.data.slices[0].data as unknown as string;
-      message.data.slices[0].data = stringToUint8Array(slice);
+      message.data.slices[0].data = binaryDecode(slice);
     }
     this.sendMessage(message.data);
   }
 
   handleCommand(method: string, requestData: Uint8Array): void {
-    const buffer = uint8ArrayToString(requestData);
-    // We need to encode the buffer as a string because the message port doesn't
-    // fully support sending ArrayBuffers (they are converted to objects with
-    // indexes as keys).
-    this.extensionPort.postMessage({method, requestData: buffer});
+    const reqEncoded = binaryEncode(requestData);
+    this.extensionPort.postMessage({method, requestData: reqEncoded});
   }
 }
diff --git a/ui/src/controller/consumer_port_types.ts b/ui/src/controller/consumer_port_types.ts
index 8a249d4..cb054e1 100644
--- a/ui/src/controller/consumer_port_types.ts
+++ b/ui/src/controller/consumer_port_types.ts
@@ -24,15 +24,15 @@
     Typed, perfetto.protos.IEnableTracingResponse {}
 export interface GetTraceStatsResponse extends
     Typed, perfetto.protos.IGetTraceStatsResponse {}
+export interface FreeBuffersResponse extends
+    Typed, perfetto.protos.IFreeBuffersResponse {}
+export interface GetCategoriesResponse extends Typed {}
+export interface DisableTracingResponse extends
+    Typed, perfetto.protos.IDisableTracingResponse {}
 
 export type ConsumerPortResponse =
-    EnableTracingResponse|ReadBuffersResponse|GetTraceStatsResponse;
-
-export function isConsumerPortResponse(obj: Typed):
-    obj is ConsumerPortResponse {
-  return isReadBuffersResponse(obj) || isEnableTracingResponse(obj) ||
-      isGetTraceStatsResponse(obj);
-}
+    EnableTracingResponse|ReadBuffersResponse|GetTraceStatsResponse|
+    GetCategoriesResponse|FreeBuffersResponse|DisableTracingResponse;
 
 export function isReadBuffersResponse(obj: Typed): obj is ReadBuffersResponse {
   return obj.type === 'ReadBuffersResponse';
@@ -46,4 +46,18 @@
 export function isGetTraceStatsResponse(obj: Typed):
     obj is GetTraceStatsResponse {
   return obj.type === 'GetTraceStatsResponse';
-}
\ No newline at end of file
+}
+
+export function isGetCategoriesResponse(obj: Typed):
+    obj is GetCategoriesResponse {
+  return obj.type === 'GetCategoriesResponse';
+}
+
+export function isFreeBuffersResponse(obj: Typed): obj is FreeBuffersResponse {
+  return obj.type === 'FreeBuffersResponse';
+}
+
+export function isDisableTracingResponse(obj: Typed):
+    obj is DisableTracingResponse {
+  return obj.type === 'DisableTracingResponse';
+}
diff --git a/ui/src/controller/cpu_profile_controller.ts b/ui/src/controller/cpu_profile_controller.ts
index d8efd0b..05e03e9 100644
--- a/ui/src/controller/cpu_profile_controller.ts
+++ b/ui/src/controller/cpu_profile_controller.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
+import {slowlyCountRows} from '../common/query_iterator';
 import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state';
 import {CpuProfileDetails} from '../frontend/globals';
 
@@ -93,69 +94,61 @@
   }
 
   async getSampleData(id: number) {
-    const sampleQuery = `SELECT samples.id, frame_name, mapping_name
+    // The goal of the query is to get all the frames of
+    // the callstack at the callsite given by |id|. To do this, it does
+    // the following:
+    // 1. Gets the leaf callsite id for the sample given by |id|.
+    // 2. For this callsite, get all the frame ids and depths
+    //    for the frame and all ancestors in the callstack.
+    // 3. For each frame, get the mapping name (i.e. library which
+    //    contains the frame).
+    // 4. Symbolize each frame using the symbol table if possible.
+    // 5. Sort the query by the depth of the callstack frames.
+    const sampleQuery = `
+      SELECT
+        samples.id,
+        IFNULL(
+          (
+            SELECT name
+            FROM stack_profile_symbol symbol
+            WHERE symbol.symbol_set_id = spf.symbol_set_id
+            LIMIT 1
+          ),
+          spf.name
+        ) AS frame_name,
+        spm.name AS mapping_name
       FROM cpu_profile_stack_sample AS samples
-      LEFT JOIN
-        (
-          SELECT
-            callsite_id,
-            position,
-            spf.name AS frame_name,
-            stack_profile_mapping.name AS mapping_name
-          FROM
-            (
-              WITH
-                RECURSIVE
-                  callsite_parser(callsite_id, current_id, position)
-                  AS (
-                    SELECT id, id, 0 FROM stack_profile_callsite
-                    UNION
-                      SELECT callsite_id, parent_id, position + 1
-                      FROM callsite_parser
-                      JOIN
-                        stack_profile_callsite
-                        ON stack_profile_callsite.id = current_id
-                      WHERE stack_profile_callsite.depth > 0
-                  )
-              SELECT *
-              FROM callsite_parser
-            ) AS flattened_callsite
-          LEFT JOIN stack_profile_callsite AS spc
-          LEFT JOIN
-            (
-              SELECT
-                spf.id AS id,
-                spf.mapping AS mapping,
-                IFNULL(
-                  (
-                    SELECT name
-                    FROM stack_profile_symbol symbol
-                    WHERE symbol.symbol_set_id = spf.symbol_set_id
-                    LIMIT 1
-                  ),
-                  spf.name
-                ) AS name
-              FROM stack_profile_frame spf
-            ) AS spf
-          LEFT JOIN stack_profile_mapping
-          WHERE
-            flattened_callsite.current_id = spc.id
-            AND spc.frame_id = spf.id
-            AND spf.mapping = stack_profile_mapping.id
-          ORDER BY callsite_id, position
-        ) AS frames
-        ON samples.callsite_id = frames.callsite_id
+      LEFT JOIN (
+        SELECT
+          id,
+          frame_id,
+          depth
+        FROM stack_profile_callsite
+        UNION ALL
+        SELECT
+          leaf.id AS id,
+          callsite.frame_id AS frame_id,
+          callsite.depth AS depth
+        FROM stack_profile_callsite leaf
+        JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite
+      ) AS callsites
+        ON samples.callsite_id = callsites.id
+      LEFT JOIN stack_profile_frame AS spf
+        ON callsites.frame_id = spf.id
+      LEFT JOIN stack_profile_mapping AS spm
+        ON spf.mapping = spm.id
       WHERE samples.id = ${id}
-      ORDER BY samples.id, frames.position DESC;`;
+      ORDER BY callsites.depth;
+    `;
 
     const callsites = await this.args.engine.query(sampleQuery);
 
-    if (callsites.numRecords < 1) {
+    if (slowlyCountRows(callsites) < 1) {
       return undefined;
     }
 
     const sampleData: CallsiteInfo[] = new Array();
-    for (let i = 0; i < callsites.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(callsites); i++) {
       const id = +callsites.columns[0].longValues![i];
       const name = callsites.columns[1].stringValues![i];
       const mapping = callsites.columns[2].stringValues![i];
@@ -168,7 +161,8 @@
         name,
         selfSize: 0,
         mapping,
-        merged: false
+        merged: false,
+        highlighted: false
       });
     }
 
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
new file mode 100644
index 0000000..86c50da
--- /dev/null
+++ b/ui/src/controller/flow_events_controller.ts
@@ -0,0 +1,196 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size 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 {Engine} from '../common/engine';
+import {slowlyCountRows} from '../common/query_iterator';
+import {Area} from '../common/state';
+import {fromNs, toNs} from '../common/time';
+import {Flow} from '../frontend/globals';
+import {Config, SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
+
+import {Controller} from './controller';
+import {globals} from './globals';
+
+export interface FlowEventsControllerArgs {
+  engine: Engine;
+}
+
+export class FlowEventsController extends Controller<'main'> {
+  private lastSelectedSliceId?: number;
+  private lastSelectedArea?: Area;
+  private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE';
+
+  constructor(private args: FlowEventsControllerArgs) {
+    super('main');
+  }
+
+  queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
+    this.args.engine.query(query).then(res => {
+      const flows: Flow[] = [];
+      for (let i = 0; i < slowlyCountRows(res); i++) {
+        const beginSliceId = res.columns[0].longValues![i];
+        const beginTrackId = res.columns[1].longValues![i];
+        const beginSliceName = res.columns[2].stringValues![i];
+        const beginSliceCategory = res.columns[3].stringValues![i];
+        const beginSliceStartTs = fromNs(res.columns[4].longValues![i]);
+        const beginSliceEndTs = fromNs(res.columns[5].longValues![i]);
+        const beginDepth = res.columns[6].longValues![i];
+
+        const endSliceId = res.columns[7].longValues![i];
+        const endTrackId = res.columns[8].longValues![i];
+        const endSliceName = res.columns[9].stringValues![i];
+        const endSliceCategory = res.columns[10].stringValues![i];
+        const endSliceStartTs = fromNs(res.columns[11].longValues![i]);
+        const endSliceEndTs = fromNs(res.columns[12].longValues![i]);
+        const endDepth = res.columns[13].longValues![i];
+
+        // Category and name present only in version 1 flow events
+        // It is most likelly NULL for all other versions
+        const category = res.columns[14].isNulls![i] ?
+            undefined :
+            res.columns[14].stringValues![i];
+        const name = res.columns[15].isNulls![i] ?
+            undefined :
+            res.columns[15].stringValues![i];
+        const id = res.columns[16].longValues![i];
+
+        flows.push({
+          id,
+          begin: {
+            trackId: beginTrackId,
+            sliceId: beginSliceId,
+            sliceName: beginSliceName,
+            sliceCategory: beginSliceCategory,
+            sliceStartTs: beginSliceStartTs,
+            sliceEndTs: beginSliceEndTs,
+            depth: beginDepth
+          },
+          end: {
+            trackId: endTrackId,
+            sliceId: endSliceId,
+            sliceName: endSliceName,
+            sliceCategory: endSliceCategory,
+            sliceStartTs: endSliceStartTs,
+            sliceEndTs: endSliceEndTs,
+            depth: endDepth
+          },
+          category,
+          name
+        });
+      }
+      callback(flows);
+    });
+  }
+
+  sliceSelected(sliceId: number) {
+    if (this.lastSelectedKind === 'CHROME_SLICE' &&
+        this.lastSelectedSliceId === sliceId) {
+      return;
+    }
+    this.lastSelectedSliceId = sliceId;
+    this.lastSelectedKind = 'CHROME_SLICE';
+
+    const query = `
+    select
+      f.slice_out, t1.track_id, t1.name,
+      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
+      f.slice_in, t2.track_id, t2.name,
+      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
+      extract_arg(f.arg_set_id, 'cat'),
+      extract_arg(f.arg_set_id, 'name'),
+      f.id
+    from connected_flow(${sliceId}) f
+    join slice t1 on f.slice_out = t1.slice_id
+    join slice t2 on f.slice_in = t2.slice_id
+    `;
+    this.queryFlowEvents(
+        query, (flows: Flow[]) => globals.publish('ConnectedFlows', flows));
+  }
+
+  areaSelected(areaId: string) {
+    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) {
+      return;
+    }
+
+    this.lastSelectedArea = area;
+    this.lastSelectedKind = 'AREA';
+
+    const trackIds: number[] = [];
+
+    for (const uiTrackId of area.tracks) {
+      if (globals.state.tracks[uiTrackId] &&
+          globals.state.tracks[uiTrackId].kind === SLICE_TRACK_KIND) {
+        trackIds.push(
+            (globals.state.tracks[uiTrackId].config as Config).trackId);
+      }
+    }
+
+    const tracks = `(${trackIds.join(',')})`;
+
+    const startNs = toNs(area.startSec);
+    const endNs = toNs(area.endSec);
+
+    const query = `
+    select
+      f.slice_out, t1.track_id, t1.name,
+      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
+      f.slice_in, t2.track_id, t2.name,
+      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
+      extract_arg(f.arg_set_id, 'cat'),
+      extract_arg(f.arg_set_id, 'name'),
+      f.id
+    from flow f
+    join slice t1 on f.slice_out = t1.slice_id
+    join slice t2 on f.slice_in = t2.slice_id
+    where
+      (t1.track_id in ${tracks}
+        and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs}))
+      or
+      (t2.track_id in ${tracks}
+        and (t2.ts <= ${endNs} and t2.ts >= ${startNs}))
+    `;
+    this.queryFlowEvents(
+        query, (flows: Flow[]) => globals.publish('SelectedFlows', flows));
+  }
+
+  refreshVisibleFlows() {
+    const selection = globals.state.currentSelection;
+    if (!selection) {
+      this.lastSelectedKind = 'NONE';
+      globals.publish('ConnectedFlows', []);
+      globals.publish('SelectedFlows', []);
+      return;
+    }
+
+    if (selection && selection.kind === 'CHROME_SLICE') {
+      this.sliceSelected(selection.id);
+    } else {
+      globals.publish('ConnectedFlows', []);
+    }
+
+    if (selection && selection.kind === 'AREA') {
+      this.areaSelected(selection.areaId);
+    } else {
+      globals.publish('SelectedFlows', []);
+    }
+  }
+
+  run() {
+    this.refreshVisibleFlows();
+  }
+}
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 0442ceb..606dab4 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -24,7 +24,8 @@
     'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapProfileDetails'|
     'HeapProfileFlamegraph'|'FileDownload'|'Loading'|'Search'|'BufferUsage'|
     'RecordingLog'|'SearchResult'|'AggregateData'|'CpuProfileDetails'|
-    'TraceErrors';
+    'TraceErrors'|'UpdateChromeCategories'|'ConnectedFlows'|'SelectedFlows'|
+    'ThreadStateDetails'|'MetricError'|'MetricResult';
 
 export interface App {
   state: State;
diff --git a/ui/src/controller/heap_profile_controller.ts b/ui/src/controller/heap_profile_controller.ts
index ea7357f..c152590 100644
--- a/ui/src/controller/heap_profile_controller.ts
+++ b/ui/src/controller/heap_profile_controller.ts
@@ -23,6 +23,7 @@
   OBJECTS_ALLOCATED_NOT_FREED_KEY,
   SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY
 } from '../common/flamegraph_util';
+import {slowlyCountRows} from '../common/query_iterator';
 import {CallsiteInfo, HeapProfileFlamegraph} from '../common/state';
 import {fromNs} from '../common/time';
 import {HeapProfileDetails} from '../frontend/globals';
@@ -216,15 +217,16 @@
       // id, name, parent_id, depth, total_size
       const tableName =
           await this.prepareViewsAndTables(ts, upid, type, focusRegex);
-      currentData =
-          await this.getFlamegraphDataFromTables(tableName, viewingOption);
+      currentData = await this.getFlamegraphDataFromTables(
+          tableName, viewingOption, focusRegex);
       this.flamegraphDatasets.set(key, currentData);
     }
     return currentData;
   }
 
   async getFlamegraphDataFromTables(
-      tableName: string, viewingOption = DEFAULT_VIEWING_OPTION) {
+      tableName: string, viewingOption = DEFAULT_VIEWING_OPTION,
+      focusRegex: string) {
     let orderBy = '';
     let sizeIndex = 4;
     let selfIndex = 9;
@@ -272,7 +274,7 @@
 
     const flamegraphData: CallsiteInfo[] = new Array();
     const hashToindex: Map<number, number> = new Map();
-    for (let i = 0; i < callsites.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(callsites); i++) {
       const hash = callsites.columns[0].longValues![i];
       let name = callsites.columns[1].stringValues![i];
       const parentHash = callsites.columns[2].longValues![i];
@@ -280,6 +282,8 @@
       const totalSize = +callsites.columns[sizeIndex].longValues![i];
       const mapping = callsites.columns[8].stringValues![i];
       const selfSize = +callsites.columns[selfIndex].longValues![i];
+      const highlighted = focusRegex !== '' &&
+          name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase());
       const parentId =
           hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1;
       if (depth === maxDepth - 1) {
@@ -297,7 +301,8 @@
         name,
         selfSize,
         mapping,
-        merged: false
+        merged: false,
+        highlighted
       });
     }
     return flamegraphData;
@@ -324,8 +329,9 @@
   getMinSizeDisplayed(flamegraphData: CallsiteInfo[], rootSize?: number):
       number {
     const timeState = globals.state.frontendLocalState.visibleState;
-    const width =
-        (timeState.endSec - timeState.startSec) / timeState.resolution;
+    let width = (timeState.endSec - timeState.startSec) / timeState.resolution;
+    // TODO(168048193): Remove screen size hack:
+    width = Math.max(width, 800);
     if (rootSize === undefined) {
       rootSize = findRootSize(flamegraphData);
     }
diff --git a/ui/src/controller/logs_controller.ts b/ui/src/controller/logs_controller.ts
index de9824f..7d1d49f 100644
--- a/ui/src/controller/logs_controller.ts
+++ b/ui/src/controller/logs_controller.ts
@@ -20,6 +20,7 @@
   LogEntriesKey,
   LogExistsKey
 } from '../common/logs';
+import {slowlyCountRows} from '../common/query_iterator';
 import {fromNs, TimeSpan, toNsCeil, toNsFloor} from '../common/time';
 
 import {Controller} from './controller';
@@ -73,7 +74,7 @@
         order by ts
         limit ${pagination.start}, ${pagination.count}`);
 
-  if (!rowsResult.numRecords) {
+  if (!slowlyCountRows(rowsResult)) {
     return {
       offset: pagination.start,
       timestamps: [],
diff --git a/ui/src/controller/metrics_controller.ts b/ui/src/controller/metrics_controller.ts
new file mode 100644
index 0000000..6d0e835
--- /dev/null
+++ b/ui/src/controller/metrics_controller.ts
@@ -0,0 +1,78 @@
+// 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.
+
+import {Actions} from '../common/actions';
+import {Engine, QueryError} from '../common/engine';
+import {iter, STR} from '../common/query_iterator';
+
+import {Controller} from './controller';
+import {globals} from './globals';
+
+export class MetricsController extends Controller<'main'> {
+  private engine: Engine;
+  private currentlyRunningMetric?: string;
+
+  constructor(args: {engine: Engine}) {
+    super('main');
+    this.engine = args.engine;
+    this.setup().finally(() => {
+      this.run();
+    });
+  }
+
+  private async getMetricNames() {
+    const metrics = [];
+    const it = iter(
+        {
+          name: STR,
+        },
+        await this.engine.query('select name from trace_metrics'));
+    for (; it.valid(); it.next()) {
+      metrics.push(it.row.name);
+    }
+    return metrics;
+  }
+
+  private async setup() {
+    const metrics = await this.getMetricNames();
+    globals.dispatch(Actions.setAvailableMetrics({metrics}));
+  }
+
+  private async computeMetric(name: string) {
+    if (name === this.currentlyRunningMetric) return;
+    this.currentlyRunningMetric = name;
+    try {
+      const metricResult = await this.engine.computeMetric([name]);
+      globals.publish(
+          'MetricResult',
+          {name, resultString: metricResult.metricsAsPrototext});
+    } catch (e) {
+      if (e instanceof QueryError) {
+        // Reroute error to be displated differently when metric is run through
+        // metric page.
+        globals.publish('MetricResult', {name, error: e.message});
+      } else {
+        throw e;
+      }
+    }
+    globals.dispatch(Actions.resetMetricRequest({name}));
+    this.currentlyRunningMetric = undefined;
+  }
+
+  run() {
+    const {requestedMetric} = globals.state.metrics;
+    if (!requestedMetric) return;
+    this.computeMetric(requestedMetric);
+  }
+}
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index d991a89..a390443 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -13,16 +13,21 @@
 // limitations under the License.
 
 import {produce} from 'immer';
-import * as uuidv4 from 'uuid/v4';
 
-import {assertExists, assertTrue} from '../base/logging';
+import {assertExists} from '../base/logging';
 import {Actions} from '../common/actions';
-import {State} from '../common/state';
+import {createEmptyState, State} from '../common/state';
+import {RecordConfig, STATE_VERSION} from '../common/state';
+import {
+  BUCKET_NAME,
+  saveState,
+  saveTrace,
+  toSha256
+} from '../common/upload_utils';
 
 import {Controller} from './controller';
 import {globals} from './globals';
-
-export const BUCKET_NAME = 'perfetto-ui-data';
+import {validateRecordConfig} from './validate_config';
 
 export class PermalinkController extends Controller<'main'> {
   private lastRequestId?: string;
@@ -38,106 +43,117 @@
     const requestId = assertExists(globals.state.permalink.requestId);
     this.lastRequestId = requestId;
 
-    // if the |link| is not set, this is a request to create a permalink.
+    // if the |hash| is not set, this is a request to create a permalink.
     if (globals.state.permalink.hash === undefined) {
-      PermalinkController.createPermalink().then(hash => {
-        globals.dispatch(Actions.setPermalink({requestId, hash}));
-      });
+      const isRecordingConfig =
+          assertExists(globals.state.permalink.isRecordingConfig);
+
+      PermalinkController.createPermalink(isRecordingConfig)
+          .then(((hash: string) => {
+            globals.dispatch(Actions.setPermalink({requestId, hash}));
+          }));
       return;
     }
 
     // Otherwise, this is a request to load the permalink.
-    PermalinkController.loadState(globals.state.permalink.hash).then(state => {
-      globals.dispatch(Actions.setState({newState: state}));
-      this.lastRequestId = state.permalink.requestId;
-    });
+    PermalinkController.loadState(globals.state.permalink.hash)
+        .then(stateOrConfig => {
+          if (PermalinkController.isRecordConfig(stateOrConfig)) {
+            // This permalink state only contains a RecordConfig. Show the
+            // recording page with the config, but keep other state as-is.
+            const validConfig = validateRecordConfig(stateOrConfig);
+            if (validConfig.errorMessage) {
+              // TODO(bsebastien): Show a warning message to the user in the UI.
+              console.warn(validConfig.errorMessage);
+            }
+            globals.dispatch(
+                Actions.setRecordConfig({config: validConfig.config}));
+            globals.dispatch(Actions.navigate({route: '/record'}));
+            return;
+          }
+          globals.dispatch(Actions.setState({newState: stateOrConfig}));
+          this.lastRequestId = stateOrConfig.permalink.requestId;
+        });
   }
 
-  private static async createPermalink() {
-    const engines = Object.values(globals.state.engines);
-    assertTrue(engines.length === 1);
-    const engine = engines[0];
-    let dataToUpload: File|ArrayBuffer|undefined = undefined;
-    let traceName = `trace ${engine.id}`;
-    if (engine.source.type === 'FILE') {
-      dataToUpload = engine.source.file;
-      traceName = dataToUpload.name;
-    } else if (engine.source.type === 'ARRAY_BUFFER') {
-      dataToUpload = engine.source.buffer;
-    } else if (engine.source.type !== 'URL') {
-      throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`);
-    }
-
-    let uploadState = globals.state;
-    if (dataToUpload !== undefined) {
-      PermalinkController.updateStatus(`Uploading ${traceName}`);
-      const url = await this.saveTrace(dataToUpload);
-      // Convert state to use URLs and remove permalink.
-      uploadState = produce(globals.state, draft => {
-        draft.engines[engine.id].source = {type: 'URL', url};
-        draft.permalink = {};
-      });
-    }
-
-    // Upload state.
-    PermalinkController.updateStatus(`Creating permalink...`);
-    const hash = await this.saveState(uploadState);
-    PermalinkController.updateStatus(`Permalink ready`);
-    return hash;
-  }
-
-  private static async saveState(state: State): Promise<string> {
-    const text = JSON.stringify(state);
-    const hash = await this.toSha256(text);
-    const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
-        `${BUCKET_NAME}/o?uploadType=media` +
-        `&name=${hash}&predefinedAcl=publicRead`;
-    const response = await fetch(url, {
-      method: 'post',
-      headers: {
-        'Content-Type': 'application/json; charset=utf-8',
-      },
-      body: text,
-    });
-    await response.json();
-
-    return hash;
-  }
-
-  private static async saveTrace(trace: File|ArrayBuffer): Promise<string> {
-    // TODO(hjd): This should probably also be a hash but that requires
-    // trace processor support.
-    const name = uuidv4();
-    const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
-        `${BUCKET_NAME}/o?uploadType=media` +
-        `&name=${name}&predefinedAcl=publicRead`;
-    const response = await fetch(url, {
-      method: 'post',
-      headers: {'Content-Type': 'application/octet-stream;'},
-      body: trace,
-    });
-    await response.json();
-    return `https://storage.googleapis.com/${BUCKET_NAME}/${name}`;
-  }
-
-  private static async loadState(id: string): Promise<State> {
-    const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`;
-    const response = await fetch(url);
-    const text = await response.text();
-    const stateHash = await this.toSha256(text);
-    const state = JSON.parse(text);
-    if (stateHash !== id) {
-      throw new Error(`State hash does not match ${id} vs. ${stateHash}`);
+  private static upgradeState(state: State): State {
+    if (state.version !== STATE_VERSION) {
+      const newState = createEmptyState();
+      // Copy the URL of the trace into the empty state.
+      for (const cfg of Object.values(state.engines)) {
+        newState
+            .engines[cfg.id] = {id: cfg.id, ready: false, source: cfg.source};
+      }
+      const message = `Unable to parse old state version. Discarding state ` +
+          `and loading trace.`;
+      console.warn(message);
+      PermalinkController.updateStatus(message);
+      return newState;
     }
     return state;
   }
 
-  private static async toSha256(str: string): Promise<string> {
-    // TODO(hjd): TypeScript bug with definition of TextEncoder.
-    // tslint:disable-next-line no-any
-    const buffer = new (TextEncoder as any)('utf-8').encode(str);
-    const digest = await crypto.subtle.digest('SHA-256', buffer);
-    return Array.from(new Uint8Array(digest)).map(x => x.toString(16)).join('');
+  private static isRecordConfig(stateOrConfig: State|
+                                RecordConfig): stateOrConfig is RecordConfig {
+    return ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'].includes(
+        stateOrConfig.mode);
+  }
+
+  private static async createPermalink(isRecordingConfig: boolean) {
+    let uploadState: State|RecordConfig = globals.state;
+
+    if (isRecordingConfig) {
+      uploadState = globals.state.recordConfig;
+    } else {
+      const engine = assertExists(Object.values(globals.state.engines)[0]);
+      let dataToUpload: File|ArrayBuffer|undefined = undefined;
+      let traceName = `trace ${engine.id}`;
+      if (engine.source.type === 'FILE') {
+        dataToUpload = engine.source.file;
+        traceName = dataToUpload.name;
+      } else if (engine.source.type === 'ARRAY_BUFFER') {
+        dataToUpload = engine.source.buffer;
+      } else if (engine.source.type !== 'URL') {
+        throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`);
+      }
+
+      if (dataToUpload !== undefined) {
+        PermalinkController.updateStatus(`Uploading ${traceName}`);
+        const url = await saveTrace(dataToUpload);
+        // Convert state to use URLs and remove permalink.
+        uploadState = produce(globals.state, draft => {
+          draft.engines[engine.id].source = {type: 'URL', url};
+          draft.permalink = {};
+        });
+      }
+    }
+
+    // Upload state.
+    PermalinkController.updateStatus(`Creating permalink...`);
+    const hash = await saveState(uploadState);
+    PermalinkController.updateStatus(`Permalink ready`);
+    return hash;
+  }
+
+  private static async loadState(id: string): Promise<State|RecordConfig> {
+    const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`;
+    const response = await fetch(url);
+    if (!response.ok) {
+      throw new Error(
+          `Could not fetch permalink.\n` +
+          `Are you sure the id (${id}) is correct?\n` +
+          `URL: ${url}`);
+    }
+    const text = await response.text();
+    const stateHash = await toSha256(text);
+    const state = JSON.parse(text);
+    if (stateHash !== id) {
+      throw new Error(`State hash does not match ${id} vs. ${stateHash}`);
+    }
+    if (!this.isRecordConfig(state)) {
+      return this.upgradeState(state);
+    }
+    return state;
   }
 
   private static updateStatus(msg: string): void {
diff --git a/ui/src/controller/query_controller.ts b/ui/src/controller/query_controller.ts
index d1c8314..c4bd9d4 100644
--- a/ui/src/controller/query_controller.ts
+++ b/ui/src/controller/query_controller.ts
@@ -17,6 +17,7 @@
 import {Engine} from '../common/engine';
 import {rawQueryResultColumns, rawQueryResultIter, Row} from '../common/protos';
 import {QueryResponse} from '../common/queries';
+import {slowlyCountRows} from '../common/query_iterator';
 
 import {Controller} from './controller';
 import {globals} from './globals';
@@ -55,7 +56,7 @@
 
   private async runQuery(sqlQuery: string) {
     const startMs = performance.now();
-    const rawResult = await this.args.engine.query(sqlQuery, true);
+    const rawResult = await this.args.engine.uncheckedQuery(sqlQuery);
     const durationMs = performance.now() - startMs;
     const columns = rawQueryResultColumns(rawResult);
     const rows =
@@ -65,7 +66,7 @@
       query: sqlQuery,
       durationMs,
       error: rawResult.error,
-      totalRowCount: +rawResult.numRecords,
+      totalRowCount: slowlyCountRows(rawResult),
       columns,
       rows,
     };
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index b037f77..d6aa111 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -15,7 +15,7 @@
 import {Message, Method, rpc, RPCImplCallback} from 'protobufjs';
 
 import {
-  uint8ArrayToBase64,
+  base64Encode,
 } from '../base/string_utils';
 import {Actions} from '../common/actions';
 import {
@@ -41,6 +41,7 @@
   isAdbTarget,
   isAndroidP,
   isChromeTarget,
+  isCrOSTarget,
   MAX_TIME,
   RecordConfig,
   RecordingTarget
@@ -53,7 +54,9 @@
 import {
   ConsumerPortResponse,
   GetTraceStatsResponse,
+  isDisableTracingResponse,
   isEnableTracingResponse,
+  isFreeBuffersResponse,
   isGetTraceStatsResponse,
   isReadBuffersResponse,
 } from './consumer_port_types';
@@ -117,17 +120,15 @@
   let procThreadAssociationFtrace = false;
   let trackInitialOomScore = false;
 
-  if (uiCfg.cpuSched || uiCfg.cpuLatency) {
+  if (uiCfg.cpuSched) {
     procThreadAssociationPolling = true;
     procThreadAssociationFtrace = true;
     ftraceEvents.add('sched/sched_switch');
     ftraceEvents.add('power/suspend_resume');
-    if (uiCfg.cpuLatency) {
-      ftraceEvents.add('sched/sched_wakeup');
-      ftraceEvents.add('sched/sched_wakeup_new');
-      ftraceEvents.add('sched/sched_waking');
-      ftraceEvents.add('power/suspend_resume');
-    }
+    ftraceEvents.add('sched/sched_wakeup');
+    ftraceEvents.add('sched/sched_wakeup_new');
+    ftraceEvents.add('sched/sched_waking');
+    ftraceEvents.add('power/suspend_resume');
   }
 
   if (uiCfg.cpuFreq) {
@@ -140,6 +141,17 @@
     ftraceEvents.add('power/gpu_frequency');
   }
 
+  if (uiCfg.gpuMemTotal) {
+    ftraceEvents.add('gpu_mem/gpu_mem_total');
+
+    if (!isChromeTarget(target) || isCrOSTarget(target)) {
+      const ds = new TraceConfig.DataSource();
+      ds.config = new DataSourceConfig();
+      ds.config.name = 'android.gpu.memory';
+      protoCfg.dataSources.push(ds);
+    }
+  }
+
   if (uiCfg.cpuSyscall) {
     ftraceEvents.add('raw_syscalls/sys_enter');
     ftraceEvents.add('raw_syscalls/sys_exit');
@@ -164,7 +176,9 @@
       AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT,
     ];
     ds.config.androidPowerConfig.collectPowerRails = true;
-    protoCfg.dataSources.push(ds);
+    if (!isChromeTarget(target) || isCrOSTarget(target)) {
+      protoCfg.dataSources.push(ds);
+    }
   }
 
   if (uiCfg.boardSensors) {
@@ -255,6 +269,8 @@
         cdc.dumpPhaseMs = uiCfg.hpContinuousDumpsPhase;
       }
     }
+    // TODO(fmayer): Add a toggle for this to the UI?
+    cfg.blockClient = true;
     heapprofd = cfg;
   }
 
@@ -292,7 +308,9 @@
     if (procThreadAssociationPolling || trackInitialOomScore) {
       ds.config.processStatsConfig.scanAllProcessesOnStart = true;
     }
-    protoCfg.dataSources.push(ds);
+    if (!isChromeTarget(target) || isCrOSTarget(target)) {
+      protoCfg.dataSources.push(ds);
+    }
   }
 
   if (uiCfg.androidLogs) {
@@ -305,7 +323,9 @@
       return AndroidLogId[name as any as number] as any as number;
     });
 
-    protoCfg.dataSources.push(ds);
+    if (!isChromeTarget(target) || isCrOSTarget(target)) {
+      protoCfg.dataSources.push(ds);
+    }
   }
 
   if (uiCfg.chromeLogs) {
@@ -368,10 +388,17 @@
     } else {
       chromeRecordMode = 'record-continuously';
     }
-    const traceConfigJson = JSON.stringify({
+    const configStruct = {
       record_mode: chromeRecordMode,
       included_categories: [...chromeCategories.values()],
-    });
+      memory_dump_config: {},
+    };
+    if (chromeCategories.has('disabled-by-default-memory-infra')) {
+      configStruct.memory_dump_config = {
+        triggers: [{mode: 'detailed', periodic_interval_ms: 10000}]
+      };
+    }
+    const traceConfigJson = JSON.stringify(configStruct);
 
     const traceDs = new TraceConfig.DataSource();
     traceDs.config = new DataSourceConfig();
@@ -395,7 +422,8 @@
 
   // Keep these last. The stages above can enrich them.
 
-  if (sysStatsCfg !== undefined) {
+  if (sysStatsCfg !== undefined &&
+      (!isChromeTarget(target) || isCrOSTarget(target))) {
     const ds = new TraceConfig.DataSource();
     ds.config = new DataSourceConfig();
     ds.config.name = 'linux.sys_stats';
@@ -403,7 +431,8 @@
     protoCfg.dataSources.push(ds);
   }
 
-  if (heapprofd !== undefined) {
+  if (heapprofd !== undefined &&
+      (!isChromeTarget(target) || isCrOSTarget(target))) {
     const ds = new TraceConfig.DataSource();
     ds.config = new DataSourceConfig();
     ds.config.targetBuffer = 0;
@@ -412,7 +441,8 @@
     protoCfg.dataSources.push(ds);
   }
 
-  if (javaHprof !== undefined) {
+  if (javaHprof !== undefined &&
+      (!isChromeTarget(target) || isCrOSTarget(target))) {
     const ds = new TraceConfig.DataSource();
     ds.config = new DataSourceConfig();
     ds.config.targetBuffer = 0;
@@ -468,7 +498,9 @@
     ds.config.ftraceConfig.ftraceEvents = ftraceEventsArray;
     ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats);
     ds.config.ftraceConfig.atraceApps = Array.from(atraceApps);
-    protoCfg.dataSources.push(ds);
+    if (!isChromeTarget(target) || isCrOSTarget(target)) {
+      protoCfg.dataSources.push(ds);
+    }
   }
 
   return protoCfg;
@@ -557,6 +589,14 @@
   }
 
   run() {
+    // TODO(eseckler): Use ConsumerPort's QueryServiceState instead
+    // of posting a custom extension message to retrieve the category list.
+    if (this.app.state.updateChromeCategories === true) {
+      if (this.app.state.extensionInstalled) {
+        this.extensionPort.postMessage({method: 'GetCategories'});
+      }
+      globals.dispatch(Actions.setUpdateChromeCategories({update: false}));
+    }
     if (this.app.state.recordConfig === this.config &&
         this.app.state.recordingInProgress === this.recordingInProgress) {
       return;
@@ -566,7 +606,7 @@
     const configProto =
         genConfigProto(this.config, this.app.state.recordingTarget);
     const configProtoText = toPbtxt(configProto);
-    const configProtoBase64 = uint8ArrayToBase64(configProto);
+    const configProtoBase64 = base64Encode(configProto);
     const commandline = `
       echo '${configProtoBase64}' |
       base64 --decode |
@@ -634,6 +674,10 @@
       if (percentage) {
         globals.publish('BufferUsage', {percentage});
       }
+    } else if (isFreeBuffersResponse(data)) {
+      // No action required.
+    } else if (isDisableTracingResponse(data)) {
+      // No action required.
     } else {
       console.error('Unrecognized consumer port response:', data);
     }
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index 07f1487..6d6e780 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -126,7 +126,33 @@
 
   const expectedTraceConfig = '{"record_mode":"record-until-full",' +
       '"included_categories":' +
-      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"]}';
+      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"],' +
+      '"memory_dump_config":{}}';
+  expect(traceConfigM).toEqual(expectedTraceConfig);
+  expect(traceConfig).toEqual(expectedTraceConfig);
+});
+
+test('ChromeMemoryConfig', () => {
+  const config = createEmptyRecordConfig();
+  config.chromeCategoriesSelected = ['disabled-by-default-memory-infra'];
+  const result =
+      TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'}));
+  const sources = assertExists(result.dataSources);
+
+  const traceConfigSource = assertExists(sources[0].config);
+  expect(traceConfigSource.name).toBe('org.chromium.trace_event');
+  const chromeConfig = assertExists(traceConfigSource.chromeConfig);
+  const traceConfig = assertExists(chromeConfig.traceConfig);
+
+  const metadataConfigSource = assertExists(sources[1].config);
+  expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata');
+  const chromeConfigM = assertExists(metadataConfigSource.chromeConfig);
+  const traceConfigM = assertExists(chromeConfigM.traceConfig);
+
+  const expectedTraceConfig = '{"record_mode":"record-until-full",' +
+      '"included_categories":["disabled-by-default-memory-infra"],' +
+      '"memory_dump_config":{"triggers":' +
+      '[{"mode":"detailed","periodic_interval_ms":10000}]}}';
   expect(traceConfigM).toEqual(expectedTraceConfig);
   expect(traceConfig).toEqual(expectedTraceConfig);
 });
@@ -152,7 +178,8 @@
 
   const expectedTraceConfig = '{"record_mode":"record-continuously",' +
       '"included_categories":' +
-      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"]}';
+      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"],' +
+      '"memory_dump_config":{}}';
   expect(traceConfigM).toEqual(expectedTraceConfig);
   expect(traceConfig).toEqual(expectedTraceConfig);
 });
@@ -179,7 +206,8 @@
 
   const expectedTraceConfig = '{"record_mode":"record-continuously",' +
       '"included_categories":' +
-      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"]}';
+      '["toplevel","disabled-by-default-ipc.flow","mojom","v8"],' +
+      '"memory_dump_config":{}}';
   expect(traceConfigM).toEqual(expectedTraceConfig);
   expect(traceConfig).toEqual(expectedTraceConfig);
 });
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index b81977c..c5ee827 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
+import {slowlyCountRows} from '../common/query_iterator';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 import {TimeSpan} from '../common/time';
 
@@ -21,7 +22,10 @@
 
 export function escapeQuery(s: string): string {
   // See https://www.sqlite.org/lang_expr.html#:~:text=A%20string%20constant
-  return `'%${s.replace('\'', '\'\'')}%'`;
+  s = s.replace(/\'/g, '\'\'');
+  s = s.replace(/_/g, '^_');
+  s = s.replace(/%/g, '^%');
+  return `'%${s}%' escape '^'`;
 }
 
 export interface SearchControllerArgs {
@@ -168,7 +172,7 @@
           group by quantum_ts
           order by quantum_ts;`);
 
-    const numRows = +rawResult.numRecords;
+    const numRows = slowlyCountRows(rawResult);
     const summary = {
       tsStarts: new Float64Array(numRows),
       tsEnds: new Float64Array(numRows),
@@ -195,10 +199,16 @@
         cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id);
         continue;
       }
-      if (track.kind === 'ChromeSliceTrack' ||
-          track.kind === 'AsyncSliceTrack') {
-        engineTrackIdToTrackId.set(
-            (track.config as {trackId: number}).trackId, track.id);
+      if (track.kind === 'ChromeSliceTrack') {
+        const config = (track.config as {trackId: number});
+        engineTrackIdToTrackId.set(config.trackId, track.id);
+        continue;
+      }
+      if (track.kind === 'AsyncSliceTrack') {
+        const config = (track.config as {trackIds: number[]});
+        for (const trackId of config.trackIds) {
+          engineTrackIdToTrackId.set(trackId, track.id);
+        }
         continue;
       }
     }
@@ -225,16 +235,25 @@
       track_id as source_id,
       0 as utid
       from slice
-      inner join track on slice.track_id = track.id
-      and slice.name like ${searchLiteral}
+      where slice.name like ${searchLiteral}
+    union
+    select
+      slice_id,
+      ts,
+      'track' as source,
+      track_id as source_id,
+      0 as utid
+      from slice
+      join args using(arg_set_id)
+      where string_value like ${searchLiteral}
     order by ts`);
 
-    const numRows = +rawResult.numRecords;
+    const numRows = slowlyCountRows(rawResult);
 
     const searchResults: CurrentSearchResults = {
-      sliceIds: new Float64Array(numRows),
-      tsStarts: new Float64Array(numRows),
-      utids: new Float64Array(numRows),
+      sliceIds: [],
+      tsStarts: [],
+      utids: [],
       trackIds: [],
       sources: [],
       totalResults: +numRows,
@@ -258,9 +277,9 @@
 
       searchResults.trackIds.push(trackId);
       searchResults.sources.push(source);
-      searchResults.sliceIds[row] = +columns[0].longValues![row];
-      searchResults.tsStarts[row] = +columns[1].longValues![row];
-      searchResults.utids[row] = +columns[4].longValues![row];
+      searchResults.sliceIds.push(+columns[0].longValues![row]);
+      searchResults.tsStarts.push(+columns[1].longValues![row]);
+      searchResults.utids.push(+columns[4].longValues![row]);
     }
     return searchResults;
   }
diff --git a/ui/src/controller/search_controller_unittest.ts b/ui/src/controller/search_controller_unittest.ts
index e3774b4..9dec627 100644
--- a/ui/src/controller/search_controller_unittest.ts
+++ b/ui/src/controller/search_controller_unittest.ts
@@ -15,7 +15,8 @@
 import {escapeQuery} from './search_controller';
 
 test('escapeQuery', () => {
-  expect(escapeQuery(``)).toEqual(`'%%'`);
-  expect(escapeQuery(`hello`)).toEqual(`'%hello%'`);
-  expect(escapeQuery('foo\'bar')).toEqual(`'%foo''bar%'`);
+  expect(escapeQuery(``)).toEqual(`'%%' escape '^'`);
+  expect(escapeQuery(`hello`)).toEqual(`'%hello%' escape '^'`);
+  expect(escapeQuery('foo\'bar')).toEqual(`'%foo''bar%' escape '^'`);
+  expect(escapeQuery('%_%')).toEqual(`'%^%^_^%%' escape '^'`);
 });
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index db85155..562fd29 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -13,8 +13,16 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
+import {slowlyCountRows} from '../common/query_iterator';
+import {translateState} from '../common/thread_state';
 import {fromNs, toNs} from '../common/time';
-import {Arg, Args, CounterDetails, SliceDetails} from '../frontend/globals';
+import {
+  Arg,
+  Args,
+  CounterDetails,
+  SliceDetails,
+  ThreadStateDetails
+} from '../frontend/globals';
 import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
 
 import {Controller} from './controller';
@@ -35,21 +43,10 @@
 
   run() {
     const selection = globals.state.currentSelection;
-    if (!selection) return;
-    // TODO(taylori): Ideally thread_state should not be special cased, it
-    // should have some form of id like everything else.
-    if (selection.kind === 'THREAD_STATE') {
-      const sqlQuery = `SELECT id FROM sched WHERE utid = ${selection.utid}
-                        and ts = ${toNs(selection.ts)}`;
-      this.args.engine.query(sqlQuery).then(result => {
-        if (result.columns[0].longValues!.length === 0) return;
-        this.sliceDetails(+result.columns[0].longValues![0]);
-      });
-      this.lastSelectedKind = selection.kind;
-      return;
-    }
+    if (!selection || selection.kind === 'AREA') return;
 
-    const selectWithId = ['SLICE', 'COUNTER', 'CHROME_SLICE', 'HEAP_PROFILE'];
+    const selectWithId =
+        ['SLICE', 'COUNTER', 'CHROME_SLICE', 'HEAP_PROFILE', 'THREAD_STATE'];
     if (!selectWithId.includes(selection.kind) ||
         (selectWithId.includes(selection.kind) &&
          selection.id === this.lastSelectedId &&
@@ -76,6 +73,8 @@
           });
     } else if (selection.kind === 'SLICE') {
       this.sliceDetails(selectedId as number);
+    } else if (selection.kind === 'THREAD_STATE') {
+      this.threadStateDetails(selection.id);
     } else if (selection.kind === 'CHROME_SLICE') {
       const table = selection.table;
       let sqlQuery = `
@@ -94,7 +93,7 @@
       this.args.engine.query(sqlQuery).then(result => {
         // Check selection is still the same on completion of query.
         const selection = globals.state.currentSelection;
-        if (result.numRecords === 1 && selection &&
+        if (slowlyCountRows(result) === 1 && selection &&
             selection.kind === selectedKind && selection.id === selectedId) {
           const ts = result.columns[0].longValues![0];
           const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
@@ -133,7 +132,7 @@
       where slice_id = ${id}
     `;
     const result = await this.args.engine.query(query);
-    for (let i = 0; i < result.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(result); i++) {
       const description = result.columns[0].stringValues![i];
       const docLink = result.columns[1].stringValues![i];
       map.set('Description', description);
@@ -152,7 +151,7 @@
       WHERE arg_set_id = ${argId}
     `;
     const result = await this.args.engine.query(query);
-    for (let i = 0; i < result.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(result); i++) {
       const name = result.columns[0].stringValues![i];
       const value = result.columns[1].stringValues![i];
       if (name === 'destination slice id' && !isNaN(Number(value))) {
@@ -185,13 +184,40 @@
     return trackId;
   }
 
+  async threadStateDetails(id: number) {
+    const query = `SELECT ts, thread_state.dur, state, io_wait,
+    thread_state.utid, thread_state.cpu, sched.id from thread_state
+    left join sched using(ts) where thread_state.id = ${id}`;
+    this.args.engine.query(query).then(result => {
+      const selection = globals.state.currentSelection;
+      const cols = result.columns;
+      if (slowlyCountRows(result) === 1 && selection) {
+        const ts = cols[0].longValues![0];
+        const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
+        const dur = fromNs(cols[1].longValues![0]);
+        const stateStr = cols[2].stringValues![0];
+        const ioWait =
+            cols[3].isNulls![0] ? undefined : !!cols[3].longValues![0];
+        const state = translateState(stateStr, ioWait);
+        const utid = cols[4].longValues![0];
+        const cpu = cols[5].isNulls![0] ? undefined : cols[5].longValues![0];
+        const sliceId =
+            cols[6].isNulls![0] ? undefined : cols[6].longValues![0];
+        const selected: ThreadStateDetails =
+            {ts: timeFromStart, dur, state, utid, cpu, sliceId};
+        globals.publish('ThreadStateDetails', selected);
+      }
+    });
+  }
+
   async sliceDetails(id: number) {
-    const sqlQuery = `SELECT ts, dur, priority, end_state, utid, cpu FROM sched
-    WHERE id = ${id}`;
+    const sqlQuery = `SELECT ts, dur, priority, end_state, utid, cpu,
+    thread_state.id FROM sched join thread_state using(ts, utid, dur, cpu)
+    WHERE sched.id = ${id}`;
     this.args.engine.query(sqlQuery).then(result => {
       // Check selection is still the same on completion of query.
       const selection = globals.state.currentSelection;
-      if (result.numRecords === 1 && selection) {
+      if (slowlyCountRows(result) === 1 && selection) {
         const ts = result.columns[0].longValues![0];
         const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
         const dur = fromNs(result.columns[1].longValues![0]);
@@ -199,8 +225,17 @@
         const endState = result.columns[3].stringValues![0];
         const utid = result.columns[4].longValues![0];
         const cpu = result.columns[5].longValues![0];
-        const selected: SliceDetails =
-            {ts: timeFromStart, dur, priority, endState, cpu, id, utid};
+        const threadStateId = result.columns[6].longValues![0];
+        const selected: SliceDetails = {
+          ts: timeFromStart,
+          dur,
+          priority,
+          endState,
+          cpu,
+          id,
+          utid,
+          threadStateId
+        };
         this.schedulingDetails(ts, utid)
             .then(wakeResult => {
               Object.assign(selected, wakeResult);
@@ -237,8 +272,8 @@
         `select * from instants where name = 'sched_waking' limit 1`);
     const wakeup = await this.args.engine.query(
         `select * from instants where name = 'sched_wakeup' limit 1`);
-    if (waking.numRecords === 0) {
-      if (wakeup.numRecords === 0) return undefined;
+    if (slowlyCountRows(waking) === 0) {
+      if (slowlyCountRows(wakeup) === 0) return undefined;
       // Only use sched_wakeup if waking is not in the trace.
       event = 'sched_wakeup';
     }
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index a7caafa..29f8709 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -14,22 +14,15 @@
 
 import '../tracks/all_controller';
 
-import * as uuidv4 from 'uuid/v4';
-
-import {COUNTER_TRACK_KIND} from '..//tracks/counter/common';
 import {assertExists, assertTrue} from '../base/logging';
 import {
   Actions,
-  AddTrackArgs,
   DeferredAction,
 } from '../common/actions';
-import {Engine} from '../common/engine';
+import {Engine, QueryError} from '../common/engine';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
-import {NUM, NUM_NULL, rawQueryToRows, STR_NULL} from '../common/protos';
-import {
-  EngineMode,
-  SCROLLING_TRACK_GROUP,
-} from '../common/state';
+import {slowlyCountRows} from '../common/query_iterator';
+import {EngineMode} from '../common/state';
 import {toNs, toNsCeil, toNsFloor} from '../common/time';
 import {TimeSpan} from '../common/time';
 import {
@@ -38,18 +31,6 @@
   WasmEngineProxy
 } from '../common/wasm_engine_proxy';
 import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
-import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common';
-import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common';
-import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
-import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common';
-import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile/common';
-import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
-import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common';
-import {
-  PROCESS_SCHEDULING_TRACK_KIND
-} from '../tracks/process_scheduling/common';
-import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
-import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
 
 import {
   CounterAggregationController
@@ -58,6 +39,9 @@
   CpuAggregationController
 } from './aggregation/cpu_aggregation_controller';
 import {
+  SliceAggregationController
+} from './aggregation/slice_aggregation_controller';
+import {
   ThreadAggregationController
 } from './aggregation/thread_aggregation_controller';
 import {Child, Children, Controller} from './controller';
@@ -65,6 +49,10 @@
   CpuProfileController,
   CpuProfileControllerArgs
 } from './cpu_profile_controller';
+import {
+  FlowEventsController,
+  FlowEventsControllerArgs
+} from './flow_events_controller';
 import {globals} from './globals';
 import {
   HeapProfileController,
@@ -72,6 +60,7 @@
 } from './heap_profile_controller';
 import {LoadingManager} from './loading_manager';
 import {LogsController} from './logs_controller';
+import {MetricsController} from './metrics_controller';
 import {QueryController, QueryControllerArgs} from './query_controller';
 import {SearchController} from './search_controller';
 import {
@@ -88,14 +77,10 @@
   TraceStream
 } from './trace_stream';
 import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
+import {decideTracks} from './track_decider';
 
 type States = 'init'|'loading_trace'|'ready';
 
-interface ThreadSliceTrack {
-  maxDepth: number;
-  trackId: number;
-}
-
 // TraceController handles handshakes with the frontend for everything that
 // concerns a single trace. It owns the WASM trace processor engine, handles
 // tracks data and SQL queries. There is one TraceController instance for each
@@ -168,6 +153,10 @@
         childControllers.push(
             Child('selection', SelectionController, selectionArgs));
 
+        const flowEventsArgs: FlowEventsControllerArgs = {engine};
+        childControllers.push(
+            Child('flowEvents', FlowEventsController, flowEventsArgs));
+
         const cpuProfileArgs: CpuProfileControllerArgs = {engine};
         childControllers.push(
             Child('cpuProfile', CpuProfileController, cpuProfileArgs));
@@ -184,6 +173,10 @@
             ThreadAggregationController,
             {engine, kind: 'thread_state_aggregation'}));
         childControllers.push(Child(
+            'slice_aggregation',
+            SliceAggregationController,
+            {engine, kind: 'slice_aggregation'}));
+        childControllers.push(Child(
             'counter_aggregation',
             CounterAggregationController,
             {engine, kind: 'counter_aggregation'}));
@@ -198,6 +191,7 @@
         }));
         childControllers.push(
             Child('traceError', TraceErrorController, {engine}));
+        childControllers.push(Child('metrics', MetricsController, {engine}));
         return childControllers;
 
       default:
@@ -313,552 +307,32 @@
 
     await this.listThreads();
     await this.loadTimelineOverview(traceTime);
+    globals.dispatch(Actions.sortThreadTracks({}));
+    await this.selectFirstHeapProfile();
+
     return engineMode;
   }
 
+  private async selectFirstHeapProfile() {
+    const query = `select * from
+    (select distinct(ts) as ts, 'native' as type,
+        upid from heap_profile_allocation
+        union
+        select distinct(graph_sample_ts) as ts, 'graph' as type, upid from
+        heap_graph_object) order by ts limit 1`;
+    const profile = await assertExists(this.engine).query(query);
+    if (profile.numRecords !== 1) return;
+    const ts = profile.columns[0].longValues![0];
+    const type = profile.columns[1].stringValues![0];
+    const upid = profile.columns[2].longValues![0];
+    globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
+  }
+
   private async listTracks() {
     this.updateStatus('Loading tracks');
-
     const engine = assertExists<Engine>(this.engine);
-    const numGpus = await engine.getNumberOfGpus();
-    const tracksToAdd: AddTrackArgs[] = [];
-
-    const maxCpuFreq = await engine.query(`
-      select max(value)
-      from counter c
-      inner join cpu_counter_track t on c.track_id = t.id
-      where name = 'cpufreq';
-    `);
-
-    const cpus = await engine.getCpus();
-
-    for (const cpu of cpus) {
-      tracksToAdd.push({
-        engineId: this.engineId,
-        kind: CPU_SLICE_TRACK_KIND,
-        name: `Cpu ${cpu}`,
-        trackGroup: SCROLLING_TRACK_GROUP,
-        config: {
-          cpu,
-        }
-      });
-    }
-
-    for (const cpu of cpus) {
-      // Only add a cpu freq track if we have
-      // cpu freq data.
-      // TODO(taylori): Find a way to display cpu idle
-      // events even if there are no cpu freq events.
-      const cpuFreqIdle = await engine.query(`
-        select
-          id as cpu_freq_id,
-          (
-            select id
-            from cpu_counter_track
-            where name = 'cpuidle'
-            and cpu = ${cpu}
-            limit 1
-          ) as cpu_idle_id
-        from cpu_counter_track
-        where name = 'cpufreq' and cpu = ${cpu}
-        limit 1;
-      `);
-      if (cpuFreqIdle.numRecords > 0) {
-        const freqTrackId = +cpuFreqIdle.columns[0].longValues![0];
-
-        const idleTrackExists: boolean = !cpuFreqIdle.columns[1].isNulls![0];
-        const idleTrackId = idleTrackExists ?
-            +cpuFreqIdle.columns[1].longValues![0] :
-            undefined;
-
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: CPU_FREQ_TRACK_KIND,
-          name: `Cpu ${cpu} Frequency`,
-          trackGroup: SCROLLING_TRACK_GROUP,
-          config: {
-            cpu,
-            maximumValue: +maxCpuFreq.columns[0].doubleValues![0],
-            freqTrackId,
-            idleTrackId,
-          }
-        });
-      }
-    }
-
-    const rawGlobalAsyncTracks = await engine.query(`
-      SELECT
-        t.name,
-        t.track_ids,
-        MAX(experimental_slice_layout.layout_depth) as max_depth
-      FROM (
-        SELECT name, GROUP_CONCAT(track.id) AS track_ids
-        FROM track
-        WHERE track.type = "track"
-        GROUP BY name
-      ) AS t CROSS JOIN experimental_slice_layout
-      WHERE t.track_ids = experimental_slice_layout.filter_track_ids
-      GROUP BY t.track_ids;
-    `);
-    for (let i = 0; i < rawGlobalAsyncTracks.numRecords; i++) {
-      const name = rawGlobalAsyncTracks.columns[0].stringValues![i];
-      const rawTrackIds = rawGlobalAsyncTracks.columns[1].stringValues![i];
-      const trackIds = rawTrackIds.split(',').map(v => Number(v));
-      const maxDepth = +rawGlobalAsyncTracks.columns[2].longValues![i];
-      const track = {
-        engineId: this.engineId,
-        kind: ASYNC_SLICE_TRACK_KIND,
-        trackGroup: SCROLLING_TRACK_GROUP,
-        name,
-        config: {
-          maxDepth,
-          trackIds,
-        },
-      };
-      tracksToAdd.push(track);
-    }
-
-    const upidToProcessTracks = new Map();
-    const rawProcessTracks = await engine.query(`
-      SELECT
-        pt.upid,
-        pt.name,
-        pt.track_ids,
-        MAX(experimental_slice_layout.layout_depth) as max_depth
-      FROM (
-        SELECT upid, name, GROUP_CONCAT(process_track.id) AS track_ids
-        FROM process_track GROUP BY upid, name
-      ) AS pt CROSS JOIN experimental_slice_layout
-      WHERE pt.track_ids = experimental_slice_layout.filter_track_ids
-      GROUP BY pt.track_ids;
-    `);
-    for (let i = 0; i < rawProcessTracks.numRecords; i++) {
-      const upid = +rawProcessTracks.columns[0].longValues![i];
-      const name = rawProcessTracks.columns[1].stringValues![i];
-      const rawTrackIds = rawProcessTracks.columns[2].stringValues![i];
-      const trackIds = rawTrackIds.split(',').map(v => Number(v));
-      const maxDepth = +rawProcessTracks.columns[3].longValues![i];
-      const track = {
-        engineId: this.engineId,
-        kind: 'AsyncSliceTrack',
-        name,
-        config: {
-          maxDepth,
-          trackIds,
-        },
-      };
-      const tracks = upidToProcessTracks.get(upid);
-      if (tracks) {
-        tracks.push(track);
-      } else {
-        upidToProcessTracks.set(upid, [track]);
-      }
-    }
-
-    const heapProfiles = await engine.query(`
-      select distinct(upid) from heap_profile_allocation
-      union
-      select distinct(upid) from heap_graph_object`);
-
-    const heapUpids: Set<number> = new Set();
-    for (let i = 0; i < heapProfiles.numRecords; i++) {
-      const upid = heapProfiles.columns[0].longValues![i];
-      heapUpids.add(+upid);
-    }
-
-    const maxGpuFreq = await engine.query(`
-      select max(value)
-      from counter c
-      inner join gpu_counter_track t on c.track_id = t.id
-      where name = 'gpufreq';
-    `);
-
-    for (let gpu = 0; gpu < numGpus; gpu++) {
-      // Only add a gpu freq track if we have
-      // gpu freq data.
-      const freqExists = await engine.query(`
-        select id
-        from gpu_counter_track
-        where name = 'gpufreq' and gpu_id = ${gpu}
-        limit 1;
-      `);
-      if (freqExists.numRecords > 0) {
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: COUNTER_TRACK_KIND,
-          name: `Gpu ${gpu} Frequency`,
-          trackGroup: SCROLLING_TRACK_GROUP,
-          config: {
-            trackId: +freqExists.columns[0].longValues![0],
-            maximumValue: +maxGpuFreq.columns[0].doubleValues![0],
-          }
-        });
-      }
-    }
-
-    // Add global or GPU counter tracks that are not bound to any pid/tid.
-    const globalCounters = await engine.query(`
-      select name, id
-      from counter_track
-      where type = 'counter_track'
-      union
-      select name, id
-      from gpu_counter_track
-      where name != 'gpufreq'
-    `);
-    for (let i = 0; i < globalCounters.numRecords; i++) {
-      const name = globalCounters.columns[0].stringValues![i];
-      const trackId = +globalCounters.columns[1].longValues![i];
-      tracksToAdd.push({
-        engineId: this.engineId,
-        kind: COUNTER_TRACK_KIND,
-        name,
-        trackGroup: SCROLLING_TRACK_GROUP,
-        config: {
-          name,
-          trackId,
-        }
-      });
-    }
-
-    interface CounterTrack {
-      name: string;
-      trackId: number;
-      startTs?: number;
-      endTs?: number;
-    }
-
-    const counterUtids = new Map<number, CounterTrack[]>();
-    const threadCounters = await engine.query(`
-      select thread_counter_track.name, utid, thread_counter_track.id,
-      start_ts, end_ts from thread_counter_track join thread using(utid)
-      where thread_counter_track.name not in ('time_in_state')
-    `);
-    for (let i = 0; i < threadCounters.numRecords; i++) {
-      const name = threadCounters.columns[0].stringValues![i];
-      const utid = +threadCounters.columns[1].longValues![i];
-      const trackId = +threadCounters.columns[2].longValues![i];
-      let startTs = undefined;
-      let endTs = undefined;
-      if (!threadCounters.columns[3].isNulls![i]) {
-        startTs = +threadCounters.columns[3].longValues![i] / 1e9;
-      }
-      if (!threadCounters.columns[4].isNulls![i]) {
-        endTs = +threadCounters.columns[4].longValues![i] / 1e9;
-      }
-
-      const track: CounterTrack = {name, trackId, startTs, endTs};
-      const el = counterUtids.get(utid);
-      if (el === undefined) {
-        counterUtids.set(utid, [track]);
-      } else {
-        el.push(track);
-      }
-    }
-
-    const counterUpids = new Map<number, CounterTrack[]>();
-    const processCounters = await engine.query(`
-      select process_counter_track.name, upid, process_counter_track.id,
-      start_ts, end_ts from process_counter_track join process using(upid)
-    `);
-    for (let i = 0; i < processCounters.numRecords; i++) {
-      const name = processCounters.columns[0].stringValues![i];
-      const upid = +processCounters.columns[1].longValues![i];
-      const trackId = +processCounters.columns[2].longValues![i];
-      let startTs = undefined;
-      let endTs = undefined;
-      if (!processCounters.columns[3].isNulls![i]) {
-        startTs = +processCounters.columns[3].longValues![i] / 1e9;
-      }
-      if (!processCounters.columns[4].isNulls![i]) {
-        endTs = +processCounters.columns[4].longValues![i] / 1e9;
-      }
-
-      const track: CounterTrack = {name, trackId, startTs, endTs};
-      const el = counterUpids.get(upid);
-      if (el === undefined) {
-        counterUpids.set(upid, [track]);
-      } else {
-        el.push(track);
-      }
-    }
-
-    // Local experiments shows getting maxDepth separately is ~2x faster than
-    // joining with threads and processes.
-    const maxDepthQuery = await engine.query(`
-          select thread_track.utid, thread_track.id, max(depth) as maxDepth
-          from slice
-          inner join thread_track on slice.track_id = thread_track.id
-          group by thread_track.id
-        `);
-
-    const utidToThreadTrack = new Map<number, ThreadSliceTrack>();
-    for (let i = 0; i < maxDepthQuery.numRecords; i++) {
-      const utid = maxDepthQuery.columns[0].longValues![i];
-      const trackId = maxDepthQuery.columns[1].longValues![i];
-      const maxDepth = maxDepthQuery.columns[2].longValues![i];
-      utidToThreadTrack.set(utid, {maxDepth, trackId});
-    }
-
-    // Return all threads
-    // sorted by:
-    //  total cpu time *for the whole parent process*
-    //  upid
-    //  utid
-    const threadQuery = await engine.query(`
-        select
-          utid,
-          tid,
-          upid,
-          pid,
-          thread.name as threadName,
-          process.name as processName,
-          total_dur as totalDur,
-          ifnull(has_sched, false) as hasSched,
-          ifnull(has_cpu_samples, false) as hasCpuSamples
-        from
-          thread
-          left join (select utid, count(1), true as has_sched
-              from sched group by utid
-          ) using(utid)
-          left join (select utid, count(1), true as has_cpu_samples
-              from cpu_profile_stack_sample group by utid
-          ) using(utid)
-          left join process using(upid)
-          left join (select upid, sum(dur) as total_dur
-              from sched join thread using(utid)
-              group by upid
-            ) using(upid)
-          left join (select upid, sum(value) as total_cycles
-              from android_thread_time_in_state_event
-              group by upid
-            ) using(upid)
-        where utid != 0
-        group by utid, upid
-        order by total_dur desc, total_cycles desc, upid, utid`);
-
-    const upidToUuid = new Map<number, string>();
-    const utidToUuid = new Map<number, string>();
-    const addTrackGroupActions: DeferredAction[] = [];
-
-    for (const row of rawQueryToRows(threadQuery, {
-           utid: NUM,
-           upid: NUM_NULL,
-           tid: NUM_NULL,
-           pid: NUM_NULL,
-           threadName: STR_NULL,
-           processName: STR_NULL,
-           totalDur: NUM_NULL,
-           hasSched: NUM,
-           hasCpuSamples: NUM,
-         })) {
-      const utid = row.utid;
-      const tid = row.tid;
-      const upid = row.upid;
-      const pid = row.pid;
-      const threadName = row.threadName;
-      const processName = row.processName;
-      const hasSchedEvents = !!row.totalDur;
-      const threadHasSched = !!row.hasSched;
-      const threadHasCpuSamples = !!row.hasCpuSamples;
-
-      const threadTrack =
-          utid === null ? undefined : utidToThreadTrack.get(utid);
-      if (threadTrack === undefined &&
-          (upid === null || counterUpids.get(upid) === undefined) &&
-          counterUtids.get(utid) === undefined && !threadHasSched &&
-          (upid === null || upid !== null && !heapUpids.has(upid))) {
-        continue;
-      }
-
-      // Group by upid if present else by utid.
-      let pUuid = upid === null ? utidToUuid.get(utid) : upidToUuid.get(upid);
-      // These should only happen once for each track group.
-      if (pUuid === undefined) {
-        pUuid = uuidv4();
-        const summaryTrackId = uuidv4();
-        if (upid === null) {
-          utidToUuid.set(utid, pUuid);
-        } else {
-          upidToUuid.set(upid, pUuid);
-        }
-
-        const pidForColor = pid || tid || upid || utid || 0;
-        const kind = hasSchedEvents ? PROCESS_SCHEDULING_TRACK_KIND :
-                                      PROCESS_SUMMARY_TRACK;
-
-        tracksToAdd.push({
-          id: summaryTrackId,
-          engineId: this.engineId,
-          kind,
-          name: `${upid === null ? tid : pid} summary`,
-          config: {pidForColor, upid, utid},
-        });
-
-        const name =
-            getTrackName({utid, processName, pid, threadName, tid, upid});
-        const addTrackGroup = Actions.addTrackGroup({
-          engineId: this.engineId,
-          summaryTrackId,
-          name,
-          id: pUuid,
-          collapsed: !(upid !== null && heapUpids.has(upid)),
-        });
-
-        // If the track group contains a heap profile, it should be before all
-        // other processes.
-        if (upid !== null && heapUpids.has(upid)) {
-          addTrackGroupActions.unshift(addTrackGroup);
-        } else {
-          addTrackGroupActions.push(addTrackGroup);
-        }
-
-        if (upid !== null) {
-          if (heapUpids.has(upid)) {
-            tracksToAdd.push({
-              engineId: this.engineId,
-              kind: HEAP_PROFILE_TRACK_KIND,
-              name: `Heap Profile`,
-              trackGroup: pUuid,
-              config: {upid}
-            });
-          }
-
-          const counterNames = counterUpids.get(upid);
-          if (counterNames !== undefined) {
-            counterNames.forEach(element => {
-              tracksToAdd.push({
-                engineId: this.engineId,
-                kind: 'CounterTrack',
-                name: element.name,
-                trackGroup: pUuid,
-                config: {
-                  name: element.name,
-                  trackId: element.trackId,
-                  startTs: element.startTs,
-                  endTs: element.endTs,
-                }
-              });
-            });
-          }
-
-          if (upidToProcessTracks.has(upid)) {
-            for (const track of upidToProcessTracks.get(upid)) {
-              tracksToAdd.push(Object.assign(track, {trackGroup: pUuid}));
-            }
-          }
-        }
-      }
-      const counterThreadNames = counterUtids.get(utid);
-      if (counterThreadNames !== undefined) {
-        counterThreadNames.forEach(element => {
-          tracksToAdd.push({
-            engineId: this.engineId,
-            kind: 'CounterTrack',
-            name: `${threadName} (${element.name})`,
-            trackGroup: pUuid,
-            config: {
-              name: element.name,
-              trackId: element.trackId,
-              startTs: element.startTs,
-              endTs: element.endTs,
-            }
-          });
-        });
-      }
-
-      if (threadHasCpuSamples) {
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: CPU_PROFILE_TRACK_KIND,
-          name: `${threadName} (CPU Stack Samples)`,
-          trackGroup: pUuid,
-          config: {utid},
-        });
-      }
-
-      if (threadHasSched) {
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: THREAD_STATE_TRACK_KIND,
-          name: getTrackName({utid, tid, threadName}),
-          trackGroup: pUuid,
-          config: {utid}
-        });
-      }
-
-      if (threadTrack !== undefined) {
-        tracksToAdd.push({
-          engineId: this.engineId,
-          kind: SLICE_TRACK_KIND,
-          name: getTrackName({utid, tid, threadName}),
-          trackGroup: pUuid,
-          config:
-              {maxDepth: threadTrack.maxDepth, trackId: threadTrack.trackId},
-        });
-      }
-    }
-
-    const logCount = await engine.query(`select count(1) from android_logs`);
-    if (logCount.columns[0].longValues![0] > 0) {
-      tracksToAdd.push({
-        engineId: this.engineId,
-        kind: ANDROID_LOGS_TRACK_KIND,
-        name: 'Android logs',
-        trackGroup: SCROLLING_TRACK_GROUP,
-        config: {}
-      });
-    }
-
-    const annotationSliceRows = await engine.query(`
-      SELECT id, name, upid FROM annotation_slice_track`);
-    for (let i = 0; i < annotationSliceRows.numRecords; i++) {
-      const id = annotationSliceRows.columns[0].longValues![i];
-      const name = annotationSliceRows.columns[1].stringValues![i];
-      const upid = annotationSliceRows.columns[2].longValues![i];
-      tracksToAdd.push({
-        engineId: this.engineId,
-        kind: SLICE_TRACK_KIND,
-        name,
-        trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
-        config: {
-          maxDepth: 0,
-          namespace: 'annotation',
-          trackId: id,
-        },
-      });
-    }
-
-    const annotationCounterRows = await engine.query(`
-      SELECT id, name, upid, min_value, max_value
-      FROM annotation_counter_track`);
-    for (let i = 0; i < annotationCounterRows.numRecords; i++) {
-      const id = annotationCounterRows.columns[0].longValues![i];
-      const name = annotationCounterRows.columns[1].stringValues![i];
-      const upid = annotationCounterRows.columns[2].longValues![i];
-      const minimumValue = annotationCounterRows.columns[3].isNulls![i] ?
-          undefined :
-          annotationCounterRows.columns[3].doubleValues![i];
-      const maximumValue = annotationCounterRows.columns[4].isNulls![i] ?
-          undefined :
-          annotationCounterRows.columns[4].doubleValues![i];
-      tracksToAdd.push({
-        engineId: this.engineId,
-        kind: 'CounterTrack',
-        name,
-        trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
-        config: {
-          name,
-          namespace: 'annotation',
-          trackId: id,
-          minimumValue,
-          maximumValue,
-        }
-      });
-    }
-
-    addTrackGroupActions.push(Actions.addTracks({tracks: tracksToAdd}));
-    globals.dispatchMultiple(addTrackGroupActions);
+    const actions = await decideTracks(this.engineId, engine);
+    globals.dispatchMultiple(actions);
   }
 
   private async listThreads() {
@@ -873,7 +347,7 @@
         using(upid)`;
     const threadRows = await assertExists(this.engine).query(sqlQuery);
     const threads: ThreadDesc[] = [];
-    for (let i = 0; i < threadRows.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(threadRows); i++) {
       const utid = threadRows.columns[0].longValues![i];
       const tid = threadRows.columns[1].longValues![i];
       const pid = threadRows.columns[2].longValues![i];
@@ -905,7 +379,7 @@
           `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
           'group by cpu order by cpu');
       const schedData: {[key: string]: QuantizedLoad} = {};
-      for (let i = 0; i < schedRows.numRecords; i++) {
+      for (let i = 0; i < slowlyCountRows(schedRows); i++) {
         const load = schedRows.columns[0].doubleValues![i];
         const cpu = schedRows.columns[1].longValues![i];
         schedData[cpu] = {startSec, endSec, load};
@@ -938,7 +412,7 @@
          group by bucket, upid`);
 
     const slicesData: {[key: string]: QuantizedLoad[]} = {};
-    for (let i = 0; i < sliceSummaryQuery.numRecords; i++) {
+    for (let i = 0; i < slowlyCountRows(sliceSummaryQuery); i++) {
       const bucket = sliceSummaryQuery.columns[0].longValues![i];
       const upid = sliceSummaryQuery.columns[1].longValues![i];
       const load = sliceSummaryQuery.columns[2].doubleValues![i];
@@ -958,67 +432,6 @@
 
   async initialiseHelperViews() {
     const engine = assertExists<Engine>(this.engine);
-    this.updateStatus('Creating views');
-    let event = 'sched_waking';
-    const waking = await engine.query(
-        `select * from instants where name = 'sched_waking' limit 1`);
-    if (waking.numRecords === 0) {
-      // Only use sched_wakeup if sched_waking is not in the trace.
-      event = 'sched_wakeup';
-    }
-    await engine.query(`create view runnable AS
-      select
-        ts,
-        lead(ts, 1, (select end_ts from trace_bounds))
-          OVER(partition by ref order by ts) - ts as dur,
-        ref as utid
-      from instants
-      where name = '${event}'`);
-
-    // Get the first ts for each utid - whether a sched wakeup/waking
-    // or sched event.
-    await engine.query(`create view first_thread as
-      select min(ts) as ts, utid from
-      (select min(ts) as ts, utid from runnable group by utid
-       UNION
-      select min(ts) as ts,utid from sched group by utid)
-      group by utid`);
-
-    // Create an entry from first ts to either the first sched_wakeup/waking
-    // or to the end if there are no sched wakeup/ings. This means we will
-    // show all information we have even with no sched_wakeup/waking events.
-    await engine.query(`create view fill as
-      select first_thread.ts as ts,
-      coalesce(min(runnable.ts), (select end_ts from trace_bounds)) -
-      first_thread.ts as dur,
-      first_thread.utid as utid
-      from first_thread
-      LEFT JOIN runnable using(utid) group by utid`);
-
-    await engine.query(`create view full_runnable as
-        select * from runnable UNION
-        select * from fill`);
-
-    await engine.query(`create virtual table thread_span
-        using span_left_join(
-          full_runnable partitioned utid,
-          sched partitioned utid)`);
-
-    this.updateStatus('Creating thread state table');
-    // For performance reasons we need to create a table here.
-    // Once b/145350531 is fixed this should be able to revert to a
-    // view and we can recover the extra memory use.
-    await engine.query(`create table thread_state as
-      select ts, dur, utid, cpu,
-      case when end_state is not null then 'Running'
-      when lag(end_state) over ordered is not null
-      then lag(end_state) over ordered else 'R'
-      end as state
-      from thread_span window ordered as
-      (partition by utid order by ts)`);
-
-    this.updateStatus('Creating thread state index');
-    await engine.query(`create index utid_index on thread_state(utid)`);
 
     this.updateStatus('Creating annotation counter track table');
     // Create the helper tables for all the annotations related data.
@@ -1072,59 +485,69 @@
                  'android_ion',
                  'android_thread_time_in_state',
                  'android_surfaceflinger',
-                 'android_batt']) {
+                 'android_batt',
+                 'android_sysui_cuj']) {
       this.updateStatus(`Computing ${metric} metric`);
-      // We don't care about the actual result of metric here as we are just
-      // interested in the annotation tracks.
-      const metricResult = await engine.computeMetric([metric]);
-      assertTrue(metricResult.error.length === 0);
-
-      this.updateStatus(`Inserting data for ${metric} metric`);
-      const result = await engine.query(`
-        SELECT * FROM ${metric}_event LIMIT 1`);
-
-      const hasSliceName =
-          result.columnDescriptors.some(x => x.name === 'slice_name');
-      const hasDur = result.columnDescriptors.some(x => x.name === 'dur');
-      const hasUpid = result.columnDescriptors.some(x => x.name === 'upid');
-
-      const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid';
-      const upidColumnWhere = hasUpid ? 'upid' : '0';
-      if (hasSliceName && hasDur) {
-        await engine.query(`
-          INSERT INTO annotation_slice_track(name, __metric_name, upid)
-          SELECT DISTINCT
-            track_name,
-            '${metric}' as metric_name,
-            ${upidColumnSelect}
-          FROM ${metric}_event
-          WHERE track_type = 'slice'
-        `);
-        await engine.query(`
-          INSERT INTO annotation_slice(track_id, ts, dur, depth, cat, name)
-          SELECT
-            t.id AS track_id,
-            ts,
-            dur,
-            0 AS depth,
-            a.track_name as cat,
-            slice_name AS name
-          FROM ${metric}_event a
-          JOIN annotation_slice_track t
-          ON a.track_name = t.name AND t.__metric_name = '${metric}'
-          ORDER BY t.id, ts
-        `);
+      try {
+        // We don't care about the actual result of metric here as we are just
+        // interested in the annotation tracks.
+        await engine.computeMetric([metric]);
+      } catch (e) {
+        if (e instanceof QueryError) {
+          globals.publish('MetricError', 'MetricError: ' + e.message);
+          continue;
+        } else {
+          throw e;
+        }
       }
 
-      const hasValue = result.columnDescriptors.some(x => x.name === 'value');
-      if (hasValue) {
-        const minMax = await engine.query(`
+      this.updateStatus(`Inserting data for ${metric} metric`);
+      try {
+        const result = await engine.query(`
+        SELECT * FROM ${metric}_event LIMIT 1`);
+
+        const hasSliceName =
+            result.columnDescriptors.some(x => x.name === 'slice_name');
+        const hasDur = result.columnDescriptors.some(x => x.name === 'dur');
+        const hasUpid = result.columnDescriptors.some(x => x.name === 'upid');
+
+        const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid';
+        const upidColumnWhere = hasUpid ? 'upid' : '0';
+        if (hasSliceName && hasDur) {
+          await engine.query(`
+            INSERT INTO annotation_slice_track(name, __metric_name, upid)
+            SELECT DISTINCT
+              track_name,
+              '${metric}' as metric_name,
+              ${upidColumnSelect}
+            FROM ${metric}_event
+            WHERE track_type = 'slice'
+          `);
+          await engine.query(`
+            INSERT INTO annotation_slice(track_id, ts, dur, depth, cat, name)
+            SELECT
+              t.id AS track_id,
+              ts,
+              dur,
+              0 AS depth,
+              a.track_name as cat,
+              slice_name AS name
+            FROM ${metric}_event a
+            JOIN annotation_slice_track t
+            ON a.track_name = t.name AND t.__metric_name = '${metric}'
+            ORDER BY t.id, ts
+          `);
+        }
+
+        const hasValue = result.columnDescriptors.some(x => x.name === 'value');
+        if (hasValue) {
+          const minMax = await engine.query(`
           SELECT MIN(value) as min_value, MAX(value) as max_value
           FROM ${metric}_event
           WHERE ${upidColumnWhere} != 0`);
-        const min = minMax.columns[0].longValues![0];
-        const max = minMax.columns[1].longValues![0];
-        await engine.query(`
+          const min = minMax.columns[0].longValues![0];
+          const max = minMax.columns[1].longValues![0];
+          await engine.query(`
           INSERT INTO annotation_counter_track(
             name, __metric_name, min_value, max_value, upid)
           SELECT DISTINCT
@@ -1136,7 +559,7 @@
           FROM ${metric}_event
           WHERE track_type = 'counter'
         `);
-        await engine.query(`
+          await engine.query(`
           INSERT INTO annotation_counter(id, track_id, ts, value)
           SELECT
             -1 as id,
@@ -1148,6 +571,13 @@
           ON a.track_name = t.name AND t.__metric_name = '${metric}'
           ORDER BY t.id, ts
         `);
+        }
+      } catch (e) {
+        if (e instanceof QueryError) {
+          globals.publish('MetricError', 'MetricError: ' + e.message);
+        } else {
+          throw e;
+        }
       }
     }
   }
@@ -1160,35 +590,4 @@
   }
 }
 
-function getTrackName(args: Partial<{
-  utid: number,
-  processName: string | null,
-  pid: number | null,
-  threadName: string | null,
-  tid: number | null,
-  upid: number | null
-}>) {
-  const {upid, utid, processName, threadName, pid, tid} = args;
 
-  const hasUpid = upid !== undefined && upid !== null;
-  const hasUtid = utid !== undefined && utid !== null;
-  const hasProcessName = processName !== undefined && processName !== null;
-  const hasThreadName = threadName !== undefined && threadName !== null;
-  const hasTid = tid !== undefined && tid !== null;
-  const hasPid = pid !== undefined && pid !== null;
-
-  if (hasUpid && hasPid && hasProcessName) {
-    return `${processName} ${pid}`;
-  } else if (hasUpid && hasPid) {
-    return `Process ${pid}`;
-  } else if (hasThreadName && hasTid) {
-    return `${threadName} ${tid}`;
-  } else if (hasTid) {
-    return `Thread ${tid}`;
-  } else if (hasUpid) {
-    return `upid: ${upid}`;
-  } else if (hasUtid) {
-    return `utid: ${utid}`;
-  }
-  return 'Unknown';
-}
diff --git a/ui/src/controller/trace_stream.ts b/ui/src/controller/trace_stream.ts
index 75ef8d4..34c3861 100644
--- a/ui/src/controller/trace_stream.ts
+++ b/ui/src/controller/trace_stream.ts
@@ -120,13 +120,41 @@
       this.httpStream = (response.body as any).getReader();
     }
 
-    const res =
-        (await this.httpStream!.read()) as {value?: Uint8Array, done: boolean};
-    const data = res.value ? res.value : new Uint8Array();
+    let eof = false;
+    let bytesRead = 0;
+    const chunks = [];
+
+    // httpStream can return very small chunks which can slow down
+    // TraceProcessor. Here we accumulate chunks until we get at least 32mb
+    // or hit EOF.
+    while (!eof && bytesRead < 32 * 1024 * 1024) {
+      const res = (await this.httpStream!.read()) as
+          {value?: Uint8Array, done: boolean};
+      if (res.value) {
+        chunks.push(res.value);
+        bytesRead += res.value.length;
+      }
+      eof = res.done;
+    }
+
+    let data;
+    if (chunks.length === 1) {
+      data = chunks[0];
+    } else {
+      // Stitch all the chunks into one big array:
+      data = new Uint8Array(bytesRead);
+      let offset = 0;
+      for (const chunk of chunks) {
+        data.set(chunk, offset);
+        offset += chunk.length;
+      }
+    }
+
     this.bytesRead += data.length;
+
     return {
       data,
-      eof: res.done,
+      eof,
       bytesRead: this.bytesRead,
       bytesTotal: this.bytesTotal,
     };
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index 43755e2..ff27d17 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from '../base/logging';
+import {assertExists, assertTrue} from '../base/logging';
 import {Engine} from '../common/engine';
 import {Registry} from '../common/registry';
 import {TraceTime, TrackState} from '../common/state';
+import {toNs} from '../common/time';
 import {LIMIT, TrackData} from '../common/track_data';
 
 import {Controller} from './controller';
@@ -39,6 +40,12 @@
   private data?: TrackData;
   private requestingData = false;
   private queuedRequest = false;
+  private isSetup = false;
+  private lastReloadHandled = 0;
+
+  // We choose 100000 as the table size to cache as this is roughly the point
+  // where SQLite sorts start to become expensive.
+  private static readonly MIN_TABLE_SIZE_TO_CACHE = 100000;
 
   constructor(args: TrackControllerArgs) {
     super('main');
@@ -50,6 +57,14 @@
     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() {}
+
+  // Can be overriden by the track implementation to allow some one-off work
+  // when requested reload (e.g. recalculating height).
+  async onReload() {}
+
   // 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.
@@ -103,8 +118,19 @@
     return result;
   }
 
+  private shouldReload(): boolean {
+    const {lastTrackReloadRequest} = globals.state;
+    return !!lastTrackReloadRequest &&
+        this.lastReloadHandled < lastTrackReloadRequest;
+  }
+
+  private markReloadHandled() {
+    this.lastReloadHandled = globals.state.lastTrackReloadRequest || 0;
+  }
+
   shouldRequestData(traceTime: TraceTime): boolean {
     if (this.data === undefined) return true;
+    if (this.shouldReload()) return true;
 
     // If at the limit only request more data if the view has moved.
     const atLimit = this.data.length === LIMIT;
@@ -125,6 +151,85 @@
         globals.state.frontendLocalState.visibleState.resolution;
   }
 
+  // Decides, based on the the length of the trace and the number of rows
+  // provided whether a TrackController subclass should cache its quantized
+  // 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 {
+    // 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);
+
+    // 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
+    // of rows). The cost of this becomes high even for just iteration but is
+    // especially slow as quantization involves a SQLite sort on the quantized
+    // timestamp (for the group by).
+    //
+    // To get around this, we can cache a pre-quantized table which we can then
+    // in zoomed-out situations and fall back to the real table when zoomed in
+    // (which naturally constrains the amount of data by virtue of the window
+    // covering a smaller timespan)
+    //
+    // This method computes that cached table by computing an approximation for
+    // the bucket size we would use when totally zoomed out and then going a few
+    // resolution levels down which ensures that our cached table works for more
+    // than the literally most zoomed out state. Moving down a resolution level
+    // is defined as moving down a power of 2; this matches the logic in
+    // |globals.getCurResolution|.
+    //
+    // TODO(lalitm): in the future, we should consider having a whole set of
+    // quantized tables each of which cover some portion of resolution lvel
+    // range. As each table covers a large number of resolution levels, even 3-4
+    // tables should really cover the all concievable trace sizes. This set
+    // could be computed by looking at the number of events being processed one
+    // level below the cached table and computing another layer of caching if
+    // that count is too high (with respect to MIN_TABLE_SIZE_TO_CACHE).
+
+    // 4k monitors have 3840 horizontal pixels so use that for a worst case
+    // approximation of the window width.
+    const approxWidthPx = 3840;
+
+    // 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);
+
+    // 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;
+
+    // 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;
+    }
+
+    // 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);
+
+    // 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;
+
+    // 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;
+  }
+
   run() {
     const visibleState = globals.state.frontendLocalState.visibleState;
     if (visibleState === undefined || visibleState.resolution === undefined ||
@@ -138,10 +243,20 @@
         this.queuedRequest = true;
       } else {
         this.requestingData = true;
-        this.onBoundsChange(
-                visibleState.startSec - dur,
-                visibleState.endSec + dur,
-                visibleState.resolution)
+        let promise = Promise.resolve();
+        if (!this.isSetup) {
+          promise = this.onSetup();
+        } else if (this.shouldReload()) {
+          promise = this.onReload().then(() => this.markReloadHandled());
+        }
+        promise
+            .then(() => {
+              this.isSetup = true;
+              return this.onBoundsChange(
+                  visibleState.startSec - dur,
+                  visibleState.endSec + dur,
+                  visibleState.resolution);
+            })
             .then(data => {
               this.publish(data);
             })
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
new file mode 100644
index 0000000..c99c2fc
--- /dev/null
+++ b/ui/src/controller/track_decider.ts
@@ -0,0 +1,690 @@
+// 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.
+
+import * as uuidv4 from 'uuid/v4';
+
+import {
+  Actions,
+  AddTrackArgs,
+  DeferredAction,
+} from '../common/actions';
+import {Engine} from '../common/engine';
+import {
+  iter,
+  NUM,
+  NUM_NULL,
+  slowlyCountRows,
+  STR_NULL
+} from '../common/query_iterator';
+import {SCROLLING_TRACK_GROUP} from '../common/state';
+import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log/common';
+import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices/common';
+import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
+import {COUNTER_TRACK_KIND} from '../tracks/counter/common';
+import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common';
+import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile/common';
+import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
+import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common';
+import {
+  PROCESS_SCHEDULING_TRACK_KIND
+} from '../tracks/process_scheduling/common';
+import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
+import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state/common';
+
+interface ThreadSliceTrack {
+  name: string;
+  maxDepth: number;
+  trackId: number;
+}
+
+function getTrackName(args: Partial<{
+  name: string | null,
+  utid: number,
+  processName: string | null,
+  pid: number | null,
+  threadName: string | null,
+  tid: number | null,
+  upid: number | null,
+  kind: string
+}>) {
+  const {name, upid, utid, processName, threadName, pid, tid, kind} = args;
+
+  const hasName = name !== undefined && name !== null && name !== '[NULL]';
+  const hasUpid = upid !== undefined && upid !== null;
+  const hasUtid = utid !== undefined && utid !== null;
+  const hasProcessName = processName !== undefined && processName !== null;
+  const hasThreadName = threadName !== undefined && threadName !== null;
+  const hasTid = tid !== undefined && tid !== null;
+  const hasPid = pid !== undefined && pid !== null;
+  const hasKind = kind !== undefined;
+
+  // If we don't have any useful information (better than
+  // upid/utid) we show the track kind to help with tracking
+  // down where this is coming from.
+  const kindSuffix = hasKind ? ` (${kind})` : '';
+
+  if (hasName) {
+    return `${name}`;
+  } else if (hasUpid && hasPid && hasProcessName) {
+    return `${processName} ${pid}`;
+  } else if (hasUpid && hasPid) {
+    return `Process ${pid}`;
+  } else if (hasThreadName && hasTid) {
+    return `${threadName} ${tid}`;
+  } else if (hasTid) {
+    return `Thread ${tid}`;
+  } else if (hasUpid) {
+    return `upid: ${upid}${kindSuffix}`;
+  } else if (hasUtid) {
+    return `utid: ${utid}${kindSuffix}`;
+  } else if (hasKind) {
+    return `${kind}`;
+  }
+  return 'Unknown';
+}
+
+export async function decideTracks(
+    engineId: string, engine: Engine): Promise<DeferredAction[]> {
+  const numGpus = await engine.getNumberOfGpus();
+  const tracksToAdd: AddTrackArgs[] = [];
+
+  const maxCpuFreq = await engine.query(`
+    select max(value)
+    from counter c
+    inner join cpu_counter_track t on c.track_id = t.id
+    where name = 'cpufreq';
+  `);
+
+  const cpus = await engine.getCpus();
+
+  for (const cpu of cpus) {
+    tracksToAdd.push({
+      engineId,
+      kind: CPU_SLICE_TRACK_KIND,
+      name: `Cpu ${cpu}`,
+      trackGroup: SCROLLING_TRACK_GROUP,
+      config: {
+        cpu,
+      }
+    });
+  }
+
+  for (const cpu of cpus) {
+    // Only add a cpu freq track if we have
+    // cpu freq data.
+    // TODO(taylori): Find a way to display cpu idle
+    // events even if there are no cpu freq events.
+    const cpuFreqIdle = await engine.query(`
+      select
+        id as cpu_freq_id,
+        (
+          select id
+          from cpu_counter_track
+          where name = 'cpuidle'
+          and cpu = ${cpu}
+          limit 1
+        ) as cpu_idle_id
+      from cpu_counter_track
+      where name = 'cpufreq' and cpu = ${cpu}
+      limit 1;
+    `);
+    if (slowlyCountRows(cpuFreqIdle) > 0) {
+      const freqTrackId = +cpuFreqIdle.columns[0].longValues![0];
+
+      const idleTrackExists: boolean = !cpuFreqIdle.columns[1].isNulls![0];
+      const idleTrackId =
+          idleTrackExists ? +cpuFreqIdle.columns[1].longValues![0] : undefined;
+
+      tracksToAdd.push({
+        engineId,
+        kind: CPU_FREQ_TRACK_KIND,
+        name: `Cpu ${cpu} Frequency`,
+        trackGroup: SCROLLING_TRACK_GROUP,
+        config: {
+          cpu,
+          maximumValue: +maxCpuFreq.columns[0].doubleValues![0],
+          freqTrackId,
+          idleTrackId,
+        }
+      });
+    }
+  }
+
+  const rawGlobalAsyncTracks = await engine.query(`
+    SELECT
+      t.name,
+      t.track_ids,
+      MAX(experimental_slice_layout.layout_depth) as max_depth
+    FROM (
+      SELECT name, GROUP_CONCAT(track.id) AS track_ids
+      FROM track
+      WHERE track.type = "track"
+      GROUP BY name
+    ) AS t CROSS JOIN experimental_slice_layout
+    WHERE t.track_ids = experimental_slice_layout.filter_track_ids
+    GROUP BY t.track_ids;
+  `);
+  for (let i = 0; i < slowlyCountRows(rawGlobalAsyncTracks); i++) {
+    const name = rawGlobalAsyncTracks.columns[0].stringValues![i];
+    const rawTrackIds = rawGlobalAsyncTracks.columns[1].stringValues![i];
+    const trackIds = rawTrackIds.split(',').map(v => Number(v));
+    const maxDepth = +rawGlobalAsyncTracks.columns[2].longValues![i];
+    const track = {
+      engineId,
+      kind: ASYNC_SLICE_TRACK_KIND,
+      trackGroup: SCROLLING_TRACK_GROUP,
+      name,
+      config: {
+        maxDepth,
+        trackIds,
+      },
+    };
+    tracksToAdd.push(track);
+  }
+
+  const upidToProcessTracks = new Map();
+  const rawProcessTracks = await engine.query(`
+    SELECT upid, name, GROUP_CONCAT(process_track.id) AS track_ids
+    FROM process_track
+    GROUP BY upid, name
+  `);
+  for (let i = 0; i < slowlyCountRows(rawProcessTracks); i++) {
+    const upid = +rawProcessTracks.columns[0].longValues![i];
+    const name = rawProcessTracks.columns[1].stringValues![i];
+    const rawTrackIds = rawProcessTracks.columns[2].stringValues![i];
+    const trackIds = rawTrackIds.split(',').map(v => Number(v));
+
+    const depthResult = await engine.query(`
+      SELECT MAX(layout_depth) as max_depth
+      FROM experimental_slice_layout('${rawTrackIds}');
+    `);
+    const maxDepth = +depthResult.columns[0].longValues![0];
+    const kind = ASYNC_SLICE_TRACK_KIND;
+    const track = {
+      engineId,
+      kind,
+      name,
+      config: {
+        maxDepth,
+        trackIds,
+      },
+    };
+    const tracks = upidToProcessTracks.get(upid);
+    if (tracks) {
+      tracks.push(track);
+    } else {
+      upidToProcessTracks.set(upid, [track]);
+    }
+  }
+
+  const heapProfiles = await engine.query(`
+    select distinct(upid) from heap_profile_allocation
+    union
+    select distinct(upid) from heap_graph_object`);
+
+  const heapUpids: Set<number> = new Set();
+  for (let i = 0; i < slowlyCountRows(heapProfiles); i++) {
+    const upid = heapProfiles.columns[0].longValues![i];
+    heapUpids.add(+upid);
+  }
+
+  const maxGpuFreq = await engine.query(`
+    select max(value)
+    from counter c
+    inner join gpu_counter_track t on c.track_id = t.id
+    where name = 'gpufreq';
+  `);
+
+  for (let gpu = 0; gpu < numGpus; gpu++) {
+    // Only add a gpu freq track if we have
+    // gpu freq data.
+    const freqExists = await engine.query(`
+      select id
+      from gpu_counter_track
+      where name = 'gpufreq' and gpu_id = ${gpu}
+      limit 1;
+    `);
+    if (slowlyCountRows(freqExists) > 0) {
+      tracksToAdd.push({
+        engineId,
+        kind: COUNTER_TRACK_KIND,
+        name: `Gpu ${gpu} Frequency`,
+        trackGroup: SCROLLING_TRACK_GROUP,
+        config: {
+          trackId: +freqExists.columns[0].longValues![0],
+          maximumValue: +maxGpuFreq.columns[0].doubleValues![0],
+        }
+      });
+    }
+  }
+
+  // Add global or GPU counter tracks that are not bound to any pid/tid.
+  const globalCounters = await engine.query(`
+    select name, id
+    from counter_track
+    where type = 'counter_track'
+    union
+    select name, id
+    from gpu_counter_track
+    where name != 'gpufreq'
+  `);
+  for (let i = 0; i < slowlyCountRows(globalCounters); i++) {
+    const name = globalCounters.columns[0].stringValues![i];
+    const trackId = +globalCounters.columns[1].longValues![i];
+    tracksToAdd.push({
+      engineId,
+      kind: COUNTER_TRACK_KIND,
+      name,
+      trackGroup: SCROLLING_TRACK_GROUP,
+      config: {
+        name,
+        trackId,
+      }
+    });
+  }
+
+  interface CounterTrack {
+    name: string;
+    trackId: number;
+    startTs?: number;
+    endTs?: number;
+  }
+
+  const counterUtids = new Map<number, CounterTrack[]>();
+  const threadCounters = await engine.query(`
+    select thread_counter_track.name, utid, thread_counter_track.id,
+    start_ts, end_ts from thread_counter_track join thread using(utid)
+    where thread_counter_track.name not in ('time_in_state')
+  `);
+  for (let i = 0; i < slowlyCountRows(threadCounters); i++) {
+    const name = threadCounters.columns[0].stringValues![i];
+    const utid = +threadCounters.columns[1].longValues![i];
+    const trackId = +threadCounters.columns[2].longValues![i];
+    let startTs = undefined;
+    let endTs = undefined;
+    if (!threadCounters.columns[3].isNulls![i]) {
+      startTs = +threadCounters.columns[3].longValues![i] / 1e9;
+    }
+    if (!threadCounters.columns[4].isNulls![i]) {
+      endTs = +threadCounters.columns[4].longValues![i] / 1e9;
+    }
+
+    const track: CounterTrack = {name, trackId, startTs, endTs};
+    const el = counterUtids.get(utid);
+    if (el === undefined) {
+      counterUtids.set(utid, [track]);
+    } else {
+      el.push(track);
+    }
+  }
+
+  const counterUpids = new Map<number, CounterTrack[]>();
+  const processCounters = await engine.query(`
+    select process_counter_track.name, upid, process_counter_track.id,
+    start_ts, end_ts from process_counter_track join process using(upid)
+  `);
+  for (let i = 0; i < slowlyCountRows(processCounters); i++) {
+    const name = processCounters.columns[0].stringValues![i];
+    const upid = +processCounters.columns[1].longValues![i];
+    const trackId = +processCounters.columns[2].longValues![i];
+    let startTs = undefined;
+    let endTs = undefined;
+    if (!processCounters.columns[3].isNulls![i]) {
+      startTs = +processCounters.columns[3].longValues![i] / 1e9;
+    }
+    if (!processCounters.columns[4].isNulls![i]) {
+      endTs = +processCounters.columns[4].longValues![i] / 1e9;
+    }
+
+    const track: CounterTrack = {name, trackId, startTs, endTs};
+    const el = counterUpids.get(upid);
+    if (el === undefined) {
+      counterUpids.set(upid, [track]);
+    } else {
+      el.push(track);
+    }
+  }
+
+  // Local experiments shows getting maxDepth separately is ~2x faster than
+  // joining with threads and processes.
+  const maxDepthQuery = await engine.query(`
+        select
+          thread_track.utid,
+          thread_track.id,
+          thread_track.name,
+          max(depth) as maxDepth
+        from slice
+        inner join thread_track on slice.track_id = thread_track.id
+        group by thread_track.id
+      `);
+
+  const utidToThreadTrack = new Map<number, ThreadSliceTrack[]>();
+  for (let i = 0; i < slowlyCountRows(maxDepthQuery); i++) {
+    const utid = maxDepthQuery.columns[0].longValues![i];
+    const trackId = maxDepthQuery.columns[1].longValues![i];
+    const name = maxDepthQuery.columns[2].stringValues![i];
+    const maxDepth = maxDepthQuery.columns[3].longValues![i];
+    const tracks = utidToThreadTrack.get(utid);
+    const track = {maxDepth, trackId, name};
+    if (tracks) {
+      tracks.push(track);
+    } else {
+      utidToThreadTrack.set(utid, [track]);
+    }
+  }
+
+  // For backwards compatability with older TP versions where
+  // android_thread_time_in_state_event table does not exists.
+  // TODO: remove once the track mega-query is improved.
+  const exists =
+      await engine.query(`select name from sqlite_master where type='table' and
+       name='android_thread_time_in_state_event'`);
+  if (slowlyCountRows(exists) === 0) {
+    await engine.query(`create view android_thread_time_in_state_event as
+        select null as upid, null as value where 0`);
+  }
+
+  // Return all threads
+  // sorted by:
+  //  total cpu time *for the whole parent process*
+  //  upid
+  //  utid
+  const threadQuery = await engine.query(`
+      select
+        utid,
+        tid,
+        upid,
+        pid,
+        thread.name as threadName,
+        process.name as processName,
+        total_dur as totalDur,
+        ifnull(has_sched, false) as hasSched,
+        ifnull(has_cpu_samples, false) as hasCpuSamples
+      from
+        thread
+        left join (select utid, count(1), true as has_sched
+            from sched group by utid
+        ) using(utid)
+        left join (select utid, count(1), true as has_cpu_samples
+            from cpu_profile_stack_sample group by utid
+        ) using(utid)
+        left join process using(upid)
+        left join (select upid, sum(dur) as total_dur
+            from sched join thread using(utid)
+            group by upid
+          ) using(upid)
+        left join (select upid, sum(value) as total_cycles
+            from android_thread_time_in_state_event
+            group by upid
+          ) using(upid)
+      where utid != 0
+      group by utid, upid
+      order by total_dur desc, total_cycles desc, upid, utid`);
+
+  const upidToUuid = new Map<number, string>();
+  const utidToUuid = new Map<number, string>();
+  const addTrackGroupActions: DeferredAction[] = [];
+
+  const it = iter(
+      {
+        utid: NUM,
+        upid: NUM_NULL,
+        tid: NUM_NULL,
+        pid: NUM_NULL,
+        threadName: STR_NULL,
+        processName: STR_NULL,
+        totalDur: NUM_NULL,
+        hasSched: NUM,
+        hasCpuSamples: NUM,
+      },
+      threadQuery);
+  for (let i = 0; it.valid(); ++i, it.next()) {
+    const row = it.row;
+    const utid = row.utid;
+    const tid = row.tid;
+    const upid = row.upid;
+    const pid = row.pid;
+    const threadName = row.threadName;
+    const processName = row.processName;
+    const hasSchedEvents = !!row.totalDur;
+    const threadHasSched = !!row.hasSched;
+    const threadHasCpuSamples = !!row.hasCpuSamples;
+    const isMainThread = tid === pid;
+
+    const threadTracks =
+        utid === null ? undefined : utidToThreadTrack.get(utid);
+    if (threadTracks === undefined &&
+        (upid === null || counterUpids.get(upid) === undefined) &&
+        counterUtids.get(utid) === undefined && !threadHasSched &&
+        (upid === null || upid !== null && !heapUpids.has(upid))) {
+      continue;
+    }
+
+    // Group by upid if present else by utid.
+    let pUuid = upid === null ? utidToUuid.get(utid) : upidToUuid.get(upid);
+    // These should only happen once for each track group.
+    if (pUuid === undefined) {
+      pUuid = uuidv4();
+      const summaryTrackId = uuidv4();
+      if (upid === null) {
+        utidToUuid.set(utid, pUuid);
+      } else {
+        upidToUuid.set(upid, pUuid);
+      }
+
+      const pidForColor = pid || tid || upid || utid || 0;
+      const kind = hasSchedEvents ? PROCESS_SCHEDULING_TRACK_KIND :
+                                    PROCESS_SUMMARY_TRACK;
+
+      tracksToAdd.push({
+        id: summaryTrackId,
+        engineId,
+        kind,
+        name: `${upid === null ? tid : pid} summary`,
+        config: {pidForColor, upid, utid},
+      });
+
+      const name =
+          getTrackName({utid, processName, pid, threadName, tid, upid});
+      const addTrackGroup = Actions.addTrackGroup({
+        engineId,
+        summaryTrackId,
+        name,
+        id: pUuid,
+        collapsed: !(upid !== null && heapUpids.has(upid)),
+      });
+
+      // If the track group contains a heap profile, it should be before all
+      // other processes.
+      if (upid !== null && heapUpids.has(upid)) {
+        addTrackGroupActions.unshift(addTrackGroup);
+      } else {
+        addTrackGroupActions.push(addTrackGroup);
+      }
+
+      if (upid !== null) {
+        if (heapUpids.has(upid)) {
+          tracksToAdd.push({
+            engineId,
+            kind: HEAP_PROFILE_TRACK_KIND,
+            name: `Heap Profile`,
+            trackGroup: pUuid,
+            config: {upid}
+          });
+        }
+
+        const counterNames = counterUpids.get(upid);
+        if (counterNames !== undefined) {
+          counterNames.forEach(element => {
+            const kind = COUNTER_TRACK_KIND;
+            const name = getTrackName({
+              name: element.name,
+              utid,
+              processName,
+              pid,
+              threadName,
+              tid,
+              upid,
+              kind
+            });
+            tracksToAdd.push({
+              engineId,
+              kind,
+              name,
+              trackGroup: pUuid,
+              config: {
+                name,
+                trackId: element.trackId,
+                startTs: element.startTs,
+                endTs: element.endTs,
+              }
+            });
+          });
+        }
+
+        if (upidToProcessTracks.has(upid)) {
+          for (const track of upidToProcessTracks.get(upid)) {
+            tracksToAdd.push(Object.assign(track, {
+              name: getTrackName({
+                name: track.name,
+                processName,
+                pid,
+                upid,
+                kind: track.kind,
+              }),
+              trackGroup: pUuid,
+            }));
+          }
+        }
+      }
+    }
+    const counterThreadNames = counterUtids.get(utid);
+    if (counterThreadNames !== undefined) {
+      const kind = COUNTER_TRACK_KIND;
+      counterThreadNames.forEach(element => {
+        const name =
+            getTrackName({name: element.name, utid, tid, kind, threadName});
+        tracksToAdd.push({
+          engineId,
+          kind,
+          name,
+          trackGroup: pUuid,
+          config: {
+            name,
+            trackId: element.trackId,
+            startTs: element.startTs,
+            endTs: element.endTs,
+          }
+        });
+      });
+    }
+
+    if (threadHasCpuSamples) {
+      tracksToAdd.push({
+        engineId,
+        kind: CPU_PROFILE_TRACK_KIND,
+        name: `${threadName} (CPU Stack Samples)`,
+        trackGroup: pUuid,
+        config: {utid},
+      });
+    }
+
+    if (threadHasSched) {
+      const kind = THREAD_STATE_TRACK_KIND;
+      tracksToAdd.push({
+        engineId,
+        kind,
+        name: getTrackName({utid, tid, threadName, kind}),
+        trackGroup: pUuid,
+        isMainThread,
+        config: {utid}
+      });
+    }
+
+    if (threadTracks !== undefined) {
+      const kind = SLICE_TRACK_KIND;
+      for (const config of threadTracks) {
+        tracksToAdd.push({
+          engineId,
+          kind,
+          name: getTrackName({name: config.name, utid, tid, threadName, kind}),
+          trackGroup: pUuid,
+          isMainThread,
+          config: {maxDepth: config.maxDepth, trackId: config.trackId},
+        });
+      }
+    }
+  }
+
+  const logCount = await engine.query(`select count(1) from android_logs`);
+  if (logCount.columns[0].longValues![0] > 0) {
+    tracksToAdd.push({
+      engineId,
+      kind: ANDROID_LOGS_TRACK_KIND,
+      name: 'Android logs',
+      trackGroup: SCROLLING_TRACK_GROUP,
+      config: {}
+    });
+  }
+
+  const annotationSliceRows = await engine.query(`
+    SELECT id, name, upid FROM annotation_slice_track`);
+  for (let i = 0; i < slowlyCountRows(annotationSliceRows); i++) {
+    const id = annotationSliceRows.columns[0].longValues![i];
+    const name = annotationSliceRows.columns[1].stringValues![i];
+    const upid = annotationSliceRows.columns[2].longValues![i];
+    tracksToAdd.push({
+      engineId,
+      kind: SLICE_TRACK_KIND,
+      name,
+      trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
+      config: {
+        maxDepth: 0,
+        namespace: 'annotation',
+        trackId: id,
+      },
+    });
+  }
+
+  const annotationCounterRows = await engine.query(`
+    SELECT id, name, upid, min_value, max_value
+    FROM annotation_counter_track`);
+  for (let i = 0; i < slowlyCountRows(annotationCounterRows); i++) {
+    const id = annotationCounterRows.columns[0].longValues![i];
+    const name = annotationCounterRows.columns[1].stringValues![i];
+    const upid = annotationCounterRows.columns[2].longValues![i];
+    const minimumValue = annotationCounterRows.columns[3].isNulls![i] ?
+        undefined :
+        annotationCounterRows.columns[3].doubleValues![i];
+    const maximumValue = annotationCounterRows.columns[4].isNulls![i] ?
+        undefined :
+        annotationCounterRows.columns[4].doubleValues![i];
+    tracksToAdd.push({
+      engineId,
+      kind: 'CounterTrack',
+      name,
+      trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : upidToUuid.get(upid),
+      config: {
+        name,
+        namespace: 'annotation',
+        trackId: id,
+        minimumValue,
+        maximumValue,
+      }
+    });
+  }
+
+  addTrackGroupActions.push(Actions.addTracks({tracks: tracksToAdd}));
+  return addTrackGroupActions;
+}
diff --git a/ui/src/controller/validate_config.ts b/ui/src/controller/validate_config.ts
new file mode 100644
index 0000000..015131f
--- /dev/null
+++ b/ui/src/controller/validate_config.ts
@@ -0,0 +1,57 @@
+// 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.
+
+import {createEmptyRecordConfig, RecordConfig} from '../common/state';
+
+interface RecordConfigValidationResult {
+  config: RecordConfig;
+  errorMessage?: string;
+}
+
+export function validateRecordConfig(
+    config: {[key: string]: string|number|boolean|string[]|null}):
+    RecordConfigValidationResult {
+  // Remove the keys that are not in both createEmptyRecordConfig and
+  // config.
+  const newConfig: RecordConfig = createEmptyRecordConfig();
+  const ignoredKeys: string[] = [];
+  // TODO(bsebastien): Also check that types of properties match.
+  Object.entries(newConfig).forEach(([key, value]) => {
+    if (key in config && typeof value === typeof config[key]) {
+      newConfig[key] = config[key];
+    } else {
+      ignoredKeys.push(key);
+    }
+  });
+
+  // Check if config has additional keys that are not in
+  // createEmptyRecordConfig().
+  for (const key of Object.keys(config)) {
+    if (!(key in newConfig)) {
+      ignoredKeys.push(key);
+    }
+  }
+
+  if (ignoredKeys.length > 0) {
+    // At least return an empty RecordConfig if nothing match.
+    return {
+      errorMessage: 'Warning: Loaded config contains incompatible keys.\n\
+        It may have been created with an older version of the UI.\n\
+        Ignored keys: ' +
+          ignoredKeys.join(' '),
+      config: newConfig,
+    };
+  }
+  return {config: newConfig};
+}
diff --git a/ui/src/controller/validate_config_jsdomtest.ts b/ui/src/controller/validate_config_jsdomtest.ts
new file mode 100644
index 0000000..370d54a
--- /dev/null
+++ b/ui/src/controller/validate_config_jsdomtest.ts
@@ -0,0 +1,100 @@
+// 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.
+
+import {createEmptyRecordConfig, RecordConfig} from '../common/state';
+import {validateRecordConfig} from './validate_config';
+
+test('validateRecordConfig test valid keys config', () => {
+  const config: RecordConfig = createEmptyRecordConfig();
+  const validationResult = validateRecordConfig(config);
+
+  expect('errorMessage' in validationResult).toEqual(false);
+
+  for (const key of Object.keys(validationResult.config)) {
+    expect(key in config).toEqual(true);
+  }
+});
+
+test('validateRecordConfig test no key config', () => {
+  const emptyRecord: RecordConfig = createEmptyRecordConfig();
+  const validationResult = validateRecordConfig({});
+
+  expect('errorMessage' in validationResult).toEqual(true);
+
+  for (const key of Object.keys(emptyRecord)) {
+    expect(key in validationResult.config).toEqual(true);
+  }
+});
+
+test('validateRecordConfig test some valid key config', () => {
+  const emptyRecord: RecordConfig = createEmptyRecordConfig();
+  const validationResult = validateRecordConfig({
+    'durationMs': 5.0,
+    'cpuSched': true,
+  });
+
+  expect('errorMessage' in validationResult).toEqual(true);
+
+  expect(validationResult.config.durationMs).toEqual(5.0);
+  expect(validationResult.config.cpuSched).toEqual(true);
+
+  for (const key of Object.keys(emptyRecord)) {
+    if (['durationMs', 'cpuSched'].includes(key) === false) {
+      expect(validationResult.config[key]).toEqual(emptyRecord[key]);
+    }
+    expect(key in validationResult.config).toEqual(true);
+  }
+});
+
+test('validateRecordConfig test some invalid key config', () => {
+  const emptyRecord: RecordConfig = createEmptyRecordConfig();
+  const validationResult = validateRecordConfig({
+    'durationMs': 5.0,
+    'invalidKey': 0,
+    'cpuSched': true,
+    'anotherInvalidKey': 'foobar',
+  });
+
+  expect('errorMessage' in validationResult).toEqual(true);
+
+  expect(validationResult.config.durationMs).toEqual(5.0);
+  expect(validationResult.config.cpuSched).toEqual(true);
+  expect('invalidKey' in validationResult.config).toEqual(false);
+  expect('anotherInvalidKey' in validationResult.config).toEqual(false);
+
+  for (const key of Object.keys(emptyRecord)) {
+    if (['durationMs', 'cpuSched'].includes(key) === false) {
+      expect(validationResult.config[key]).toEqual(emptyRecord[key]);
+    }
+    expect(key in validationResult.config).toEqual(true);
+  }
+});
+
+test('validateRecordConfig test only invalid key config', () => {
+  const emptyRecord: RecordConfig = createEmptyRecordConfig();
+  const validationResult = validateRecordConfig({
+    'invalidKey': 0,
+    'anotherInvalidKey': 'foobar',
+  });
+
+  expect('errorMessage' in validationResult).toEqual(true);
+
+  expect('invalidKey' in validationResult.config).toEqual(false);
+  expect('anotherInvalidKey' in validationResult.config).toEqual(false);
+
+  for (const key of Object.keys(emptyRecord)) {
+    expect(validationResult.config[key]).toEqual(emptyRecord[key]);
+    expect(key in validationResult.config).toEqual(true);
+  }
+});
diff --git a/ui/src/engine/wasm_bridge.ts b/ui/src/engine/wasm_bridge.ts
index 97a3692..d138530 100644
--- a/ui/src/engine/wasm_bridge.ts
+++ b/ui/src/engine/wasm_bridge.ts
@@ -23,10 +23,6 @@
 // HEAPU8[reqBufferAddr, +REQ_BUFFER_SIZE].
 const REQ_BUF_SIZE = 32 * 1024 * 1024;
 
-function writeToUIConsole(line: string) {
-  console.log(line);
-}
-
 export interface WasmBridgeRequest {
   id: number;
   methodName: string;
@@ -35,7 +31,6 @@
 
 export interface WasmBridgeResponse {
   id: number;
-  aborted: boolean;  // If true the WASM module crashed.
   data: Uint8Array;
 }
 
@@ -47,6 +42,7 @@
   private currentRequestResult: WasmBridgeResponse|null;
   private connection: init_trace_processor.Module;
   private reqBufferAddr = 0;
+  private lastStderr: string[] = [];
 
   constructor(init: init_trace_processor.InitWasm) {
     this.aborted = false;
@@ -55,10 +51,9 @@
     const deferredRuntimeInitialized = defer<void>();
     this.connection = init({
       locateFile: (s: string) => s,
-      print: writeToUIConsole,
-      printErr: writeToUIConsole,
+      print: (line: string) => console.log(line),
+      printErr: (line: string) => this.appendAndLogErr(line),
       onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(),
-      onAbort: () => this.aborted = true,
     });
     this.whenInitialized = deferredRuntimeInitialized.then(() => {
       const fn = this.connection.addFunction(this.onReply.bind(this), 'iii');
@@ -72,26 +67,31 @@
 
   callWasm(req: WasmBridgeRequest): WasmBridgeResponse {
     if (this.aborted) {
-      return {
-        id: req.id,
-        aborted: true,
-        data: new Uint8Array(),
-      };
+      throw new Error('Wasm module crashed');
     }
     assertTrue(req.data.length <= REQ_BUF_SIZE);
     const endAddr = this.reqBufferAddr + req.data.length;
     this.connection.HEAPU8.subarray(this.reqBufferAddr, endAddr).set(req.data);
-    this.connection.ccall(
-        req.methodName,    // C method name.
-        'void',            // Return type.
-        ['number'],        // Arg types.
-        [req.data.length]  // Args.
-    );
-
-    const result = assertExists(this.currentRequestResult);
-    this.currentRequestResult = null;
-    result.id = req.id;
-    return result;
+    try {
+      this.connection.ccall(
+          req.methodName,    // C method name.
+          'void',            // Return type.
+          ['number'],        // Arg types.
+          [req.data.length]  // Args.
+      );
+      const result = assertExists(this.currentRequestResult);
+      this.currentRequestResult = null;
+      result.id = req.id;
+      return result;
+    } catch (err) {
+      this.aborted = true;
+      let abortReason = `${err}`;
+      if (err instanceof Error) {
+        abortReason = `${err.name}: ${err.message}\n${err.stack}`;
+      }
+      abortReason += '\n\nstderr: \n' + this.lastStderr.join('\n');
+      throw new Error(abortReason);
+    }
   }
 
   // This is invoked from ccall in the same call stack as callWasm.
@@ -99,8 +99,16 @@
     const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size);
     this.currentRequestResult = {
       id: 0,  // Will be set by callWasm()'s epilogue.
-      aborted: false,
       data,
     };
   }
+
+  private appendAndLogErr(line: string) {
+    console.warn(line);
+    // Keep the last N lines in the |lastStderr| buffer.
+    this.lastStderr.push(line);
+    if (this.lastStderr.length > 512) {
+      this.lastStderr.shift();
+    }
+  }
 }
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index 87b6e97..409fda2 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -15,7 +15,12 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {AggregateData, Column} from '../common/aggregation_data';
+import {
+  AggregateData,
+  Column,
+  ThreadStateExtra
+} from '../common/aggregation_data';
+import {colorForState, textColorForState} from '../common/colorizer';
 import {translateState} from '../common/thread_state';
 
 import {globals} from './globals';
@@ -31,11 +36,19 @@
     return m(
         '.details-panel',
         m('.details-panel-heading.aggregation',
+          attrs.data.extra !== undefined &&
+                  attrs.data.extra.kind === 'THREAD_STATE' ?
+              this.showStateSummary(attrs.data.extra) :
+              null,
           this.showTimeRange(),
           m('table',
             m('tr',
               attrs.data.columns.map(
-                  col => this.formatColumnHeading(col, attrs.kind))))),
+                  col => this.formatColumnHeading(col, attrs.kind))),
+            m('tr.sum', attrs.data.columnSums.map(sum => {
+              const sumClass = sum === '' ? 'td' : 'td.sum-data';
+              return m(sumClass, sum);
+            })))),
         m(
             '.details-table.aggregation',
             m('table', this.getRows(attrs.data)),
@@ -77,24 +90,52 @@
   getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) {
     switch (data.columns[columnIndex].kind) {
       case 'STRING':
-        return `${data.strings[data.columns[columnIndex].data[rowIndex]]}`;
+        return data.strings[data.columns[columnIndex].data[rowIndex]];
       case 'TIMESTAMP_NS':
         return `${data.columns[columnIndex].data[rowIndex] / 1000000}`;
-      case 'STATE':
-        return translateState(
-            `${data.strings[data.columns[columnIndex].data[rowIndex]]}`);
+      case 'STATE': {
+        const concatState =
+            data.strings[data.columns[columnIndex].data[rowIndex]];
+        const split = concatState.split(',');
+        const ioWait =
+            split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10);
+        return translateState(split[0], ioWait);
+      }
       case 'NUMBER':
       default:
-        return `${data.columns[columnIndex].data[rowIndex]}`;
+        return data.columns[columnIndex].data[rowIndex];
     }
   }
 
   showTimeRange() {
-    const area = globals.state.frontendLocalState.selectedArea.area;
-    if (area === undefined) return undefined;
-    const rangeDurationMs = (area.endSec - area.startSec) * 1e3;
+    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;
     return m('.time-range', `Selected range: ${rangeDurationMs.toFixed(6)} ms`);
   }
 
+  // Thread state aggregation panel only
+  showStateSummary(data: ThreadStateExtra) {
+    if (data === undefined) return undefined;
+    const states = [];
+    for (let i = 0; i < data.states.length; i++) {
+      const color = colorForState(data.states[i]);
+      const textColor = textColorForState(data.states[i]);
+      const width = data.values[i] / data.totalMs * 100;
+      states.push(
+          m('.state',
+            {
+              style: {
+                background: `hsl(${color.h},${color.s}%,${color.l}%)`,
+                color: `${textColor}`,
+                width: `${width}%`
+              }
+            },
+            `${data.states[i]}: ${data.values[i]} ms`));
+    }
+    return m('.states', states);
+  }
+
   renderCanvas() {}
 }
\ No newline at end of file
diff --git a/ui/src/frontend/analytics.ts b/ui/src/frontend/analytics.ts
new file mode 100644
index 0000000..8a0ba21
--- /dev/null
+++ b/ui/src/frontend/analytics.ts
@@ -0,0 +1,56 @@
+// 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.
+
+import {globals} from '../frontend/globals';
+
+type TraceCategories = 'Trace Actions'|'Record Trace'|'User Actions';
+const ANALYTICS_ID = 'UA-137828855-1';
+
+export function initAnalytics() {
+  // Only initialize logging on prod or staging
+  if (window.location.origin.endsWith('.perfetto.dev') ||
+      window.location.origin.endsWith('staging-dot-perfetto-ui.appspot.com')) {
+    return new Analytics();
+  }
+  return new NullAnalytics();
+}
+
+export class NullAnalytics {
+  updatePath(_: string) {}
+  logEvent(_x: TraceCategories|null, _y: string) {}
+  logError(_x: string) {}
+}
+
+export class Analytics {
+  constructor() {
+    gtag('js', new Date());
+  }
+
+  updatePath(path: string) {
+    gtag('config', ANALYTICS_ID, {
+      'anonymize_ip': true,
+      'page_path': path,
+      'referrer': document.referrer.split('?')[0],
+      'dimension1': globals.isInternalUser,
+    });
+  }
+
+  logEvent(category: TraceCategories|null, event: string) {
+    gtag('event', event, {'event_category': category});
+  }
+
+  logError(description: string, fatal = true) {
+    gtag('event', 'exception', {description, fatal});
+  }
+}
diff --git a/ui/src/frontend/android_bug_tool.ts b/ui/src/frontend/android_bug_tool.ts
new file mode 100644
index 0000000..ff63f58
--- /dev/null
+++ b/ui/src/frontend/android_bug_tool.ts
@@ -0,0 +1,67 @@
+import {defer} from '../base/deferred';
+
+enum WebContentScriptMessageType {
+  UNKNOWN,
+  CONVERT_OBJECT_URL,
+  CONVERT_OBJECT_URL_RESPONSE,
+}
+
+const ANDROID_BUG_TOOL_EXTENSION_ID = 'mbbaofdfoekifkfpgehgffcpagbbjkmj';
+
+interface Attachment {
+  name: string;
+  objectUrl: string;
+  restrictionSeverity: number;
+}
+
+interface ConvertObjectUrlResponse {
+  action: WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE;
+  attachments: Attachment[];
+  issueAccessLevel: string;
+  issueId: string;
+  issueTitle: string;
+}
+
+export interface TraceFromBuganizer {
+  issueId: string;
+  issueTitle: string;
+  file: File;
+}
+
+export function loadAndroidBugToolInfo(): Promise<TraceFromBuganizer> {
+  const deferred = defer<TraceFromBuganizer>();
+
+  // Request to convert the blob object url "blob:chrome-extension://xxx"
+  // the chrome extension has to a web downloadable url "blob:http://xxx".
+  chrome.runtime.sendMessage(
+      ANDROID_BUG_TOOL_EXTENSION_ID,
+      {action: WebContentScriptMessageType.CONVERT_OBJECT_URL},
+      async (response: ConvertObjectUrlResponse) => {
+        switch (response.action) {
+          case WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE:
+          if (response.attachments?.length > 0) {
+            const filesBlobPromises =
+                response.attachments.map(async attachment => {
+                  const fileQueryResponse = await fetch(attachment.objectUrl);
+                  const blob = await fileQueryResponse.blob();
+                  // Note: The blob's media type is always set to "image/png".
+                  // Clone blob to clear media type.
+                  return new File([blob], attachment.name);
+                });
+            const files = await Promise.all(filesBlobPromises);
+            deferred.resolve({
+              issueId: response.issueId,
+              issueTitle: response.issueTitle,
+              file: files[0],
+            });
+          } else {
+            throw new Error('Got no attachements from extension');
+          }
+          break;
+          default:
+            throw new Error(`Received unhandled response code (${
+                response.action}) from extension.`);
+        }
+      });
+  return deferred;
+}
diff --git a/ui/src/frontend/cookie_consent.ts b/ui/src/frontend/cookie_consent.ts
new file mode 100644
index 0000000..3ea5ecf
--- /dev/null
+++ b/ui/src/frontend/cookie_consent.ts
@@ -0,0 +1,53 @@
+// 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.
+
+import * as m from 'mithril';
+
+import {globals} from './globals';
+
+const COOKIE_ACK_KEY = 'cookieAck';
+
+export class CookieConsent implements m.ClassComponent {
+  private showCookieConsent = true;
+
+  view() {
+    if (this.showCookieConsent) {
+      this.showCookieConsent = localStorage.getItem(COOKIE_ACK_KEY) === null;
+    }
+    if (!this.showCookieConsent) return;
+    return m(
+        '.cookie-consent',
+        m('.cookie-text',
+          `This site uses cookies from Google to deliver its services and to
+          analyze traffic.`),
+        m('.buttons',
+          m('button',
+            m('a',
+              {
+                href: 'https://policies.google.com/technologies/cookies',
+                target: '_blank'
+              },
+              'More details')),
+          m('button',
+            {
+              onclick: () => {
+                this.showCookieConsent = false;
+                localStorage.setItem(COOKIE_ACK_KEY, 'true');
+                globals.rafScheduler.scheduleFullRedraw();
+              }
+            },
+            'OK')),
+    );
+  }
+}
diff --git a/ui/src/frontend/cpu_profile_panel.ts b/ui/src/frontend/cpu_profile_panel.ts
index 0ccf69c..6bd873d 100644
--- a/ui/src/frontend/cpu_profile_panel.ts
+++ b/ui/src/frontend/cpu_profile_panel.ts
@@ -25,7 +25,7 @@
     const sampleDetails = globals.cpuProfileDetails;
     const header =
         m('.details-panel-heading', m('h2', `CPU Profile Sample Details`));
-    if (!sampleDetails || !sampleDetails.id) {
+    if (!sampleDetails || sampleDetails.id === undefined) {
       return m('.details-panel', header);
     }
 
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 32ddab0..7ede0c5 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -21,11 +21,16 @@
 import {CounterDetailsPanel} from './counter_panel';
 import {CpuProfileDetailsPanel} from './cpu_profile_panel';
 import {DragGestureHandler} from './drag_gesture_handler';
+import {
+  FlowEventsAreaSelectedPanel,
+  FlowEventsPanel
+} from './flow_events_panel';
 import {globals} from './globals';
 import {HeapProfileDetailsPanel} from './heap_profile_panel';
 import {LogPanel} from './logs_panel';
 import {NotesEditorPanel} from './notes_panel';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
+import {QueryTable} from './query_table';
 import {SliceDetailsPanel} from './slice_panel';
 import {ThreadStatePanel} from './thread_state_panel';
 
@@ -66,7 +71,10 @@
   private fullscreenHeight = DEFAULT_DETAILS_HEIGHT_PX;
   private tabNames = new Map<string, string>([
     ['current_selection', 'Current Selection'],
+    ['bound_flows', 'Flow Events'],
+    ['selected_flows', 'Flow Events'],
     ['android_logs', 'Android Logs'],
+    ['query_result', 'Query Result'],
   ]);
 
 
@@ -107,10 +115,17 @@
   view({attrs}: m.CVnode<DragHandleAttrs>) {
     const icon = this.isClosed ? UP_ICON : DOWN_ICON;
     const title = this.isClosed ? 'Show panel' : 'Hide panel';
+    const activeTabExists = globals.frontendLocalState.currentTab &&
+        attrs.tabs.includes(globals.frontendLocalState.currentTab);
+    if (!activeTabExists) {
+      globals.frontendLocalState.currentTab = undefined;
+    }
     const renderTab = (key: string) => {
       if (globals.frontendLocalState.currentTab === key ||
           globals.frontendLocalState.currentTab === undefined &&
               attrs.tabs[0] === key) {
+        // Update currentTab in case we didn't have one before.
+        globals.frontendLocalState.currentTab = key;
         return m(
             '.tab[active]',
             this.tabNames.get(key) === undefined ? key :
@@ -147,6 +162,9 @@
               onclick: () => {
                 if (this.height === DRAG_HANDLE_HEIGHT_PX) {
                   this.isClosed = false;
+                  if (this.previousHeight === 0) {
+                    this.previousHeight = DEFAULT_DETAILS_HEIGHT_PX;
+                  }
                   this.resize(this.previousHeight);
                 } else {
                   this.isFullscreen = false;
@@ -163,7 +181,7 @@
 }
 
 export class DetailsPanel implements m.ClassComponent {
-  private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
+  private detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
   // Used to set details panel to default height on selection.
   private showDetailsPanel = true;
 
@@ -178,6 +196,14 @@
                               id: curSelection.id,
                             }));
           break;
+        case 'AREA':
+          if (curSelection.noteId !== undefined) {
+            detailsPanels.set('current_selection', m(NotesEditorPanel, {
+                                key: 'area_notes',
+                                id: curSelection.noteId,
+                              }));
+          }
+          break;
         case 'SLICE':
           detailsPanels.set('current_selection', m(SliceDetailsPanel, {
                               key: 'slice',
@@ -199,40 +225,46 @@
                             }));
           break;
         case 'CHROME_SLICE':
-          detailsPanels.set('current_selection', m(ChromeSliceDetailsPanel));
+          detailsPanels.set(
+              'current_selection',
+              m(ChromeSliceDetailsPanel, {key: 'chrome_slice'}));
           break;
         case 'THREAD_STATE':
-          detailsPanels.set('current_selection', m(ThreadStatePanel, {
-                              key: 'thread_state',
-                              ts: curSelection.ts,
-                              dur: curSelection.dur,
-                              utid: curSelection.utid,
-                              state: curSelection.state,
-                              cpu: curSelection.cpu
-                            }));
+          detailsPanels.set(
+              'current_selection', m(ThreadStatePanel, {key: 'thread_state'}));
           break;
         default:
           break;
       }
     }
     if (hasLogs()) {
-      detailsPanels.set('android_logs', m(LogPanel, {}));
+      detailsPanels.set('android_logs', m(LogPanel, {key: 'logs_panel'}));
+    }
+
+    if (globals.queryResults.has('command')) {
+      detailsPanels.set(
+          'query_result', m(QueryTable, {key: 'query', queryId: 'command'}));
+    }
+
+    if (globals.connectedFlows.length > 0) {
+      detailsPanels.set(
+          'bound_flows', m(FlowEventsPanel, {key: 'flow_events'}));
     }
 
     for (const [key, value] of globals.aggregateDataStore.entries()) {
       if (value.columns.length > 0 && value.columns[0].data.length > 0) {
         detailsPanels.set(
-            value.tabName, m(AggregationPanel, {kind: key, data: value}));
+            value.tabName, m(AggregationPanel, {kind: key, key, data: value}));
       }
     }
 
-    const wasShowing = this.showDetailsPanel;
-    this.showDetailsPanel = detailsPanels.size > 0;
-    // The first time the details panel appears, it should be default height.
-    if (!wasShowing && this.showDetailsPanel) {
-      this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
+    // Add this after all aggregation panels, to make it appear after 'Slices'
+    if (globals.selectedFlows.length > 0) {
+      detailsPanels.set('selected_flows', m(FlowEventsAreaSelectedPanel));
     }
 
+    this.showDetailsPanel = detailsPanels.size > 0;
+
     const panel = globals.frontendLocalState.currentTab &&
             detailsPanels.has(globals.frontendLocalState.currentTab) ?
         detailsPanels.get(globals.frontendLocalState.currentTab) :
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index f65e0d5..7fb6deb 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -14,7 +14,12 @@
 
 import * as m from 'mithril';
 
+import {TraceUrlSource} from '../common/state';
+import {saveTrace} from '../common/upload_utils';
+
+import {globals} from './globals';
 import {showModal} from './modal';
+import {isShareable} from './trace_attrs';
 
 // Never show more than one dialog per minute.
 const MIN_REPORT_PERIOD_MS = 60000;
@@ -25,6 +30,7 @@
 const ERR_QUEUE_MAX_LEN = 10;
 
 export function maybeShowErrorDialog(errLog: string) {
+  globals.logging.logError(errLog);
   const now = performance.now();
 
   // Here we rely on the exception message from onCannotGrowMemory function
@@ -35,6 +41,17 @@
     return;
   }
 
+  if (errLog.includes('Unable to claim interface.') ||
+      errLog.includes('A transfer error has occurred')) {
+    showWebUSBError();
+    timeLastReport = now;
+  }
+
+  if (errLog.includes('(ERR:fmt)')) {
+    showUnknownFileError();
+    return;
+  }
+
   if (timeLastReport > 0 && now - timeLastReport <= MIN_REPORT_PERIOD_MS) {
     queuedErrors.unshift(errLog);
     if (queuedErrors.length > ERR_QUEUE_MAX_LEN) queuedErrors.pop();
@@ -50,30 +67,120 @@
   }
 
   const errTitle = errLog.split('\n', 1)[0].substr(0, 80);
-  let link = 'https://goto.google.com/perfetto-ui-bug';
-  link += '?title=' + encodeURIComponent(`UI Error: ${errTitle}`);
-  link += '&description=' + encodeURIComponent(errLog.substr(0, 32768));
+  const userDescription = '';
+  let checked = false;
+  const engine = Object.values(globals.state.engines)[0];
 
+  const shareTraceSection: m.Vnode[] = [];
+  if (isShareable() && !urlExists()) {
+    shareTraceSection.push(
+        m(`input[type=checkbox]`, {
+          checked,
+          oninput: (ev: InputEvent) => {
+            checked = (ev.target as HTMLInputElement).checked;
+            if (checked && engine.source.type === 'FILE') {
+              saveTrace(engine.source.file).then(url => {
+                const errMessage = createErrorMessage(errLog, checked, url);
+                renderModal(
+                    errTitle, errMessage, userDescription, shareTraceSection);
+                return;
+              });
+            }
+            const errMessage = createErrorMessage(errLog, checked);
+            renderModal(
+                errTitle, errMessage, userDescription, shareTraceSection);
+          },
+        }),
+        m('span', `Check this box to share the current trace for debugging
+     purposes.`),
+        m('div.modal-small',
+          `This will create a permalink to this trace, you may
+     leave it unchecked and attach the trace manually
+     to the bug if preferred.`));
+  }
+  renderModal(
+      errTitle,
+      createErrorMessage(errLog, checked),
+      userDescription,
+      shareTraceSection);
+}
+
+function renderModal(
+    errTitle: string,
+    errMessage: string,
+    userDescription: string,
+    shareTraceSection: m.Vnode[]) {
   showModal({
-    title: 'Oops, something went wrong. Please file a bug',
-    content: m(
-        'div',
-        m('span', 'An unexpected crash occurred:'),
-        m('.modal-logs', errLog),
-        ),
+    title: 'Oops, something went wrong. Please file a bug.',
+    content:
+        m('div',
+          m('.modal-logs', errMessage),
+          m('span', `Please provide any additional details describing
+           how the crash occurred:`),
+          m('textarea.modal-textarea', {
+            rows: 3,
+            maxlength: 1000,
+            oninput: (ev: InputEvent) => {
+              userDescription = (ev.target as HTMLTextAreaElement).value;
+            },
+            onkeydown: (e: Event) => {
+              e.stopPropagation();
+            },
+            onkeyup: (e: Event) => {
+              e.stopPropagation();
+            },
+          }),
+          shareTraceSection),
     buttons: [
       {
         text: 'File a bug (Googlers only)',
         primary: true,
         id: 'file_bug',
         action: () => {
-          window.open(link, '_blank');
+          window.open(
+              createLink(errTitle, errMessage, userDescription), '_blank');
         }
       },
     ]
   });
 }
 
+// If there is a trace URL to share, we don't have to show the upload checkbox.
+function urlExists() {
+  const engine = Object.values(globals.state.engines)[0];
+  return engine !== undefined &&
+      (engine.source.type === 'ARRAY_BUFFER' || engine.source.type === 'URL') &&
+      engine.source.url !== undefined;
+}
+
+function createErrorMessage(errLog: string, checked: boolean, url?: string) {
+  let errMessage = '';
+  const engine = Object.values(globals.state.engines)[0];
+  if (checked && url !== undefined) {
+    errMessage += `Trace: ${url}`;
+  } else if (urlExists()) {
+    errMessage += `Trace: ${(engine.source as TraceUrlSource).url}`;
+  } else {
+    errMessage += 'To assist with debugging please attach or link to the ' +
+        'trace you were viewing.';
+  }
+  return errMessage + '\n\n' +
+      'Viewed on: ' + self.location.origin + '\n\n' + errLog;
+}
+
+function createLink(
+    errTitle: string, errMessage: string, userDescription: string): string {
+  let link = 'https://goto.google.com/perfetto-ui-bug';
+  link += '?title=' + encodeURIComponent(`UI Error: ${errTitle}`);
+  link += '&description=';
+  if (userDescription !== '') {
+    link +=
+        encodeURIComponent('User description:\n' + userDescription + '\n\n');
+  }
+  link += encodeURIComponent(errMessage.substr(0, 32768));
+  return link;
+}
+
 function showOutOfMemoryDialog() {
   const url =
       'https://perfetto.dev/docs/quickstart/trace-analysis#get-trace-processor';
@@ -82,7 +189,7 @@
       'directly in the trace_processor binary.';
 
   showModal({
-    title: 'Opps! Your WASM trace processor ran out of memory',
+    title: 'Oops! Your WASM trace processor ran out of memory',
     content: m(
         'div',
         m('span', description),
@@ -96,3 +203,42 @@
     buttons: []
   });
 }
+
+function showUnknownFileError() {
+  showModal({
+    title: 'Cannot open this file',
+    content: m(
+        'div',
+        m('p',
+          'The file opened doesn\'t look like a Perfetto trace or any ' +
+              'other format recognized by the Perfetto TraceProcessor.'),
+        m('p', 'Formats supported:'),
+        m(
+            'ul',
+            m('li', 'Perfetto protobuf trace'),
+            m('li', 'chrome://tracing JSON'),
+            m('li', 'Android systrace'),
+            m('li', 'Fuchsia trace'),
+            m('li', 'Ninja build log'),
+            ),
+        ),
+    buttons: []
+  });
+}
+
+function showWebUSBError() {
+  showModal({
+    title: 'A WebUSB error occurred',
+    content: m(
+        'div',
+        m('span', `Is adb already running on the host? Run this command and
+      try again.`),
+        m('br'),
+        m('.modal-bash', '> adb kill-server'),
+        m('br'),
+        m('span', 'For details see '),
+        m('a', {href: 'http://b/159048331', target: '_blank'}, 'b/159048331'),
+        ),
+    buttons: []
+  });
+}
\ No newline at end of file
diff --git a/ui/src/frontend/flamegraph.ts b/ui/src/frontend/flamegraph.ts
index ddd8c39..77e209a 100644
--- a/ui/src/frontend/flamegraph.ts
+++ b/ui/src/frontend/flamegraph.ts
@@ -29,9 +29,10 @@
   width: number;
 }
 
-const NODE_HEIGHT_DEFAULT = 15;
+// Height of one 'row' on the flame chart including 1px of whitespace
+// below the box.
+const NODE_HEIGHT = 18;
 
-export const HEAP_PROFILE_COLOR = 'hsl(224, 45%, 70%)';
 export const HEAP_PROFILE_HOVERED_COLOR = 'hsl(224, 45%, 55%)';
 
 export function findRootSize(data: CallsiteInfo[]) {
@@ -50,12 +51,15 @@
 }
 
 export class Flamegraph {
-  private isThumbnail = false;
   private nodeRendering: NodeRendering = {};
   private flamegraphData: CallsiteInfo[];
+  private highlightSomeNodes = false;
   private maxDepth = -1;
   private totalSize = -1;
-  private textSize = 12;
+  // Initialised on first draw() call
+  private labelCharWidth = 0;
+  private labelFontStyle = '12px Roboto Mono';
+  private rolloverFontStyle = '12px Roboto Condensed';
   // Key for the map is depth followed by x coordinate - `depth;x`
   private graphData: Map<string, CallsiteInfoWidth> = new Map();
   private xStartsPerDepth: Map<number, number[]> = new Map();
@@ -76,31 +80,36 @@
     this.maxDepth = Math.max(...this.flamegraphData.map(value => value.depth));
   }
 
-  hash(s: string): number {
-    let hash = 0x811c9dc5 & 0xfffffff;
-    for (let i = 0; i < s.length; i++) {
-      hash ^= s.charCodeAt(i);
-      hash = (hash * 16777619) & 0xffffffff;
-    }
-    return hash & 0xff;
+  // Instead of highlighting the interesting nodes, we actually want to
+  // de-emphasize the non-highlighted nodes. Returns true if there
+  // are any highlighted nodes in the flamegraph.
+  private highlightingExists() {
+    this.highlightSomeNodes = this.flamegraphData.some((e) => e.highlighted);
   }
 
-  generateColor(name: string, isGreyedOut = false): string {
-    if (this.isThumbnail) {
-      return HEAP_PROFILE_COLOR;
-    }
+  generateColor(name: string, isGreyedOut = false, highlighted: boolean):
+      string {
     if (isGreyedOut) {
       return '#d9d9d9';
     }
     if (name === 'unknown' || name === 'root') {
       return '#c0c0c0';
     }
-    const hue = this.hash(name);
-    return `hsl(${hue}, 50%, 65%)`;
+    let x = 0;
+    for (let i = 0; i < name.length; i += 1) {
+      x += name.charCodeAt(i) % 64;
+    }
+    x = x % 360;
+    let l = '76';
+    // Make non-highlighted node lighter.
+    if (this.highlightSomeNodes && !highlighted) {
+      l = '90';
+    }
+    return `hsl(${x}deg, 45%, ${l}%)`;
   }
 
   /**
-   * Caller will have to call draw method ater updating data to have updated
+   * Caller will have to call draw method after updating data to have updated
    * graph.
    */
   updateDataIfChanged(
@@ -114,6 +123,7 @@
     this.flamegraphData = flamegraphData;
     this.clickedCallsite = clickedCallsite;
     this.findMaxDepth();
+    this.highlightingExists();
     // Finding total size of roots.
     this.totalSize = findRootSize(flamegraphData);
   }
@@ -121,15 +131,19 @@
   draw(
       ctx: CanvasRenderingContext2D, width: number, height: number, x = 0,
       y = 0, unit = 'B') {
-    // TODO(taylori): Instead of pesimistic approach improve displaying text.
-    const name = '____MMMMMMQQwwZZZZZZzzzzzznnnnnnwwwwwwWWWWWqq$$mmmmmm__';
-    const charWidth = ctx.measureText(name).width / name.length;
-    const nodeHeight = this.getNodeHeight();
-    this.startingY = y;
 
     if (this.flamegraphData === undefined) {
       return;
     }
+
+    ctx.font = this.labelFontStyle;
+    ctx.textBaseline = 'middle';
+    if (this.labelCharWidth === 0) {
+      this.labelCharWidth = ctx.measureText('_').width;
+    }
+
+    this.startingY = y;
+
     // For each node, we use this map to get information about it's parent
     // (total size of it, width and where it starts in graph) so we can
     // calculate it's own position in graph.
@@ -137,24 +151,26 @@
     let currentY = y;
     nodesMap.set(-1, {width, nextXForChildren: x, size: this.totalSize, x});
 
-    // Initialize data needed for click/hover behaivior.
+    // Initialize data needed for click/hover behavior.
     this.graphData = new Map();
     this.xStartsPerDepth = new Map();
 
     // Draw root node.
-    ctx.fillStyle = this.generateColor('root', false);
-    ctx.fillRect(x, currentY, width, nodeHeight);
-    ctx.font = `${this.textSize}px Roboto Condensed`;
+    ctx.fillStyle = this.generateColor('root', false, false);
+    ctx.fillRect(x, currentY, width, NODE_HEIGHT - 1);
     const text = cropText(
         `root: ${
             this.displaySize(
                 this.totalSize, unit, unit === 'B' ? 1024 : 1000)}`,
-        charWidth,
+        this.labelCharWidth,
         width - 2);
     ctx.fillStyle = 'black';
-    ctx.fillText(text, x + 5, currentY + nodeHeight - 4);
-    currentY += nodeHeight;
+    ctx.fillText(text, x + 5, currentY + (NODE_HEIGHT - 1) / 2);
+    currentY += NODE_HEIGHT;
 
+    // Set style for borders.
+    ctx.strokeStyle = 'white';
+    ctx.lineWidth = 0.5;
 
     for (let i = 0; i < this.flamegraphData.length; i++) {
       if (currentY > height) {
@@ -166,7 +182,7 @@
         continue;
       }
 
-      const isClicked = !this.isThumbnail && this.clickedCallsite !== undefined;
+      const isClicked = this.clickedCallsite !== undefined;
       const isFullWidth =
           isClicked && value.depth <= this.clickedCallsite!.depth;
       const isGreyedOut =
@@ -179,12 +195,12 @@
           (isFullWidth ? 1 : value.totalSize / parentSize) * parentNode.width;
 
       const currentX = parentNode.nextXForChildren;
-      currentY = y + nodeHeight * (value.depth + 1);
+      currentY = y + NODE_HEIGHT * (value.depth + 1);
 
       // Draw node.
       const name = this.getCallsiteName(value);
-      ctx.fillStyle = this.generateColor(name, isGreyedOut);
-      ctx.fillRect(currentX, currentY, width, nodeHeight);
+      ctx.fillStyle = this.generateColor(name, isGreyedOut, value.highlighted);
+      ctx.fillRect(currentX, currentY, width, NODE_HEIGHT - 1);
 
       // Set current node's data in map for children to use.
       nodesMap.set(value.id, {
@@ -201,27 +217,26 @@
         x: parentNode.x
       });
 
-      // Thumbnail mode doesn't have name on nodes and click/hover behaviour.
-      if (this.isThumbnail) {
-        continue;
-      }
-
       // Draw name.
-      ctx.font = `${this.textSize}px Roboto Condensed`;
-      const text = cropText(name, charWidth, width - 2);
+      const labelPaddingPx = 5;
+      const maxLabelWidth = width - labelPaddingPx * 2;
+      let text = cropText(name, this.labelCharWidth, maxLabelWidth);
+      // If cropped text and the original text are within 20% we keep the
+      // original text and just squish it a bit.
+      if (text.length * 1.2 > name.length) {
+        text = name;
+      }
       ctx.fillStyle = 'black';
-      ctx.fillText(text, currentX + 5, currentY + nodeHeight - 4);
+      ctx.fillText(
+          text,
+          currentX + labelPaddingPx,
+          currentY + (NODE_HEIGHT - 1) / 2,
+          maxLabelWidth);
 
-      // Draw border around node.
-      ctx.strokeStyle = 'white';
+      // Draw border on the right of node.
       ctx.beginPath();
-      ctx.moveTo(currentX, currentY);
-      ctx.lineTo(currentX, currentY + nodeHeight);
-      ctx.lineTo(currentX + width, currentY + nodeHeight);
-      ctx.lineTo(currentX + width, currentY);
-      ctx.moveTo(currentX, currentY);
-      ctx.lineWidth = 1;
-      ctx.closePath();
+      ctx.moveTo(currentX + width, currentY);
+      ctx.lineTo(currentX + width, currentY + NODE_HEIGHT);
       ctx.stroke();
 
       // Add this node for recognizing in click/hover.
@@ -238,13 +253,25 @@
       }
     }
 
+    // Draw the tooltip.
     if (this.hoveredX > -1 && this.hoveredY > -1 && this.hoveredCallsite) {
-      // Draw the tooltip.
+      // Must set these before measureText below.
+      ctx.font = this.rolloverFontStyle;
+      ctx.textBaseline = 'top';
+
+      // Size in px of the border around the text and the edge of the rollover
+      // background.
+      const paddingPx = 8;
+      // Size in px of the x and y offset between the mouse and the top left
+      // corner of the rollover box.
+      const offsetPx = 4;
+
       const lines: string[] = [];
       let lineSplitter: LineSplitter;
       const nameText = this.getCallsiteName(this.hoveredCallsite);
+      const nameTextSize = ctx.measureText(nameText);
       lineSplitter =
-          splitIfTooBig(nameText, width, ctx.measureText(nameText).width);
+          splitIfTooBig(nameText, width - paddingPx, nameTextSize.width);
       let textWidth = lineSplitter.lineWidth;
       lines.push(...lineSplitter.lines);
 
@@ -283,23 +310,36 @@
         lines.push(...lineSplitter.lines);
       }
 
-      const rectWidth = textWidth + 16;
-      const rectXStart = this.hoveredX + 8 + rectWidth > width ?
-          width - rectWidth - 8 :
-          this.hoveredX + 8;
-      const rectHeight = nodeHeight * (lines.length + 1);
-      const rectYStart = this.hoveredY + 4 + rectHeight > height ?
-          height - rectHeight - 8 :
-          this.hoveredY + 4;
+      // Compute a line height as the bounding box height + 50%:
+      const heightSample = ctx.measureText(
+          'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
+      const lineHeight =
+          Math.round(heightSample.actualBoundingBoxDescent * 1.5);
 
-      ctx.font = '12px Roboto Condensed';
+      const rectWidth = textWidth + 2 * paddingPx;
+      const rectHeight = lineHeight * lines.length + 2 * paddingPx;
+
+      let rectXStart = this.hoveredX + offsetPx;
+      let rectYStart = this.hoveredY + offsetPx;
+
+      if (rectXStart + rectWidth > width) {
+        rectXStart = width - rectWidth;
+      }
+
+      if (rectYStart + rectHeight > height) {
+        rectYStart = height - rectHeight;
+      }
+
       ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
       ctx.fillRect(rectXStart, rectYStart, rectWidth, rectHeight);
       ctx.fillStyle = 'hsl(200, 50%, 40%)';
       ctx.textAlign = 'left';
       for (let i = 0; i < lines.length; i++) {
         const line = lines[i];
-        ctx.fillText(line, rectXStart + 4, rectYStart + (i + 1) * 18);
+        ctx.fillText(
+            line,
+            rectXStart + paddingPx,
+            rectYStart + paddingPx + i * lineHeight);
       }
     }
   }
@@ -345,9 +385,6 @@
   }
 
   onMouseClick({x, y}: {x: number, y: number}): CallsiteInfo|undefined {
-    if (this.isThumbnail) {
-      return undefined;
-    }
     const clickedCallsite = this.findSelectedCallsite(x, y);
     // TODO(b/148596659): Allow to expand [merged] callsites. Currently,
     // this expands to the biggest of the nodes that were merged, which
@@ -359,8 +396,8 @@
   }
 
   private findSelectedCallsite(x: number, y: number): CallsiteInfo|undefined {
-    const depth = Math.trunc((y - this.startingY) / this.getNodeHeight()) -
-        1;  // at 0 is root
+    const depth =
+        Math.trunc((y - this.startingY) / NODE_HEIGHT) - 1;  // at 0 is root
     if (depth >= 0 && this.xStartsPerDepth.has(depth)) {
       const startX = this.searchSmallest(this.xStartsPerDepth.get(depth)!, x);
       const result = this.graphData.get(`${depth};${startX}`);
@@ -379,17 +416,8 @@
   }
 
   getHeight(): number {
-    return this.flamegraphData.length === 0 ?
-        0 :
-        (this.maxDepth + 2) * this.getNodeHeight();
-  }
-
-  getNodeHeight() {
-    return this.isThumbnail ? 1 : NODE_HEIGHT_DEFAULT;
-  }
-
-  enableThumbnail(isThumbnail: boolean) {
-    this.isThumbnail = isThumbnail;
+    return this.flamegraphData.length === 0 ? 0 :
+                                              (this.maxDepth + 2) * NODE_HEIGHT;
   }
 }
 
@@ -409,6 +437,6 @@
     lines.push(line.slice(0, maxLineLen));
     line = line.slice(maxLineLen);
   }
-  lineWidth = Math.min(maxWidth, lineWidth);
+  lineWidth = Math.min(maxLineLen * charWidth, lineWidth);
   return {lineWidth, lines};
 }
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
new file mode 100644
index 0000000..6f15468
--- /dev/null
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -0,0 +1,196 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size 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 * as m from 'mithril';
+
+import {Actions} from '../common/actions';
+
+import {Flow, globals} from './globals';
+import {BLANK_CHECKBOX, CHECKBOX} from './icons';
+import {Panel, PanelSize} from './panel';
+import {findUiTrackId} from './scroll_helper';
+
+export const ALL_CATEGORIES = '_all_';
+
+export function getFlowCategories(flow: Flow): string[] {
+  const categories: string[] = [];
+  // v1 flows have their own categories
+  if (flow.category) {
+    categories.push(...flow.category.split(','));
+    return categories;
+  }
+  const beginCats = flow.begin.sliceCategory.split(',');
+  const endCats = flow.end.sliceCategory.split(',');
+  categories.push(...new Set([...beginCats, ...endCats]));
+  return categories;
+}
+
+export class FlowEventsPanel extends Panel {
+  view() {
+    const selection = globals.state.currentSelection;
+    if (!selection || selection.kind !== 'CHROME_SLICE') {
+      return;
+    }
+
+    const flowClickHandler = (sliceId: number, trackId: number) => {
+      const uiTrackId = findUiTrackId(trackId);
+      if (uiTrackId) {
+        globals.makeSelection(
+            Actions.selectChromeSlice(
+                {id: sliceId, trackId: uiTrackId, table: 'slice'}),
+            'bound_flows');
+      }
+    };
+
+    // Can happen only for flow events version 1
+    const haveCategories =
+        globals.connectedFlows.filter(flow => flow.category).length > 0;
+
+    const columns = [
+      m('th', 'Direction'),
+      m('th', 'Connected Slice ID'),
+      m('th', 'Connected Slice Name')
+    ];
+
+    if (haveCategories) {
+      columns.push(m('th', 'Flow Category'));
+      columns.push(m('th', 'Flow Name'));
+    }
+
+    const rows = [m('tr', columns)];
+
+    // Fill the table with all the directly connected flow events
+    globals.connectedFlows.forEach(flow => {
+      if (selection.id !== flow.begin.sliceId &&
+          selection.id !== flow.end.sliceId) {
+        return;
+      }
+
+      const outgoing = selection.id === flow.begin.sliceId;
+      const otherEnd = (outgoing ? flow.end : flow.begin);
+
+      const args = {
+        onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId),
+        onmousemove: () =>
+            globals.frontendLocalState.setHighlightedSliceId(otherEnd.sliceId),
+        onmouseleave: () => globals.frontendLocalState.setHighlightedSliceId(-1)
+      };
+
+      const data = [
+        m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'),
+        m('td.flow-link', args, otherEnd.sliceId.toString()),
+        m('td.flow-link', args, otherEnd.sliceName)
+      ];
+
+      if (haveCategories) {
+        data.push(m('td.flow-info', flow.category || '-'));
+        data.push(m('td.flow-info', flow.name || '-'));
+      }
+
+      rows.push(m('tr', data));
+    });
+
+    return m('.details-panel', [
+      m('.details-panel-heading', m('h2', `Flow events`)),
+      m('.flow-events-table', m('table.half-width', rows))
+    ]);
+  }
+
+  renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {}
+}
+
+export class FlowEventsAreaSelectedPanel extends Panel {
+  view() {
+    const selection = globals.state.currentSelection;
+    if (!selection || selection.kind !== 'AREA') {
+      return;
+    }
+
+    const columns = [
+      m('th', 'Flow Category'),
+      m('th', 'Number of flows'),
+      m('th',
+        'Show',
+        m('a.warning',
+          m('i.material-icons', 'warning'),
+          m('.tooltip',
+            'Showing a large number of flows may impact performance.')))
+    ];
+
+    const rows = [m('tr', columns)];
+
+    const categoryToFlowsNum = new Map<string, number>();
+
+    globals.selectedFlows.forEach(flow => {
+      const categories = getFlowCategories(flow);
+      categories.forEach(cat => {
+        if (!categoryToFlowsNum.has(cat)) {
+          categoryToFlowsNum.set(cat, 0);
+        }
+        categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1);
+      });
+    });
+
+    const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES);
+    rows.push(m('tr.sum', [
+      m('td.sum-data', 'All'),
+      m('td.sum-data', globals.selectedFlows.length),
+      m('td.sum-data',
+        m('i.material-icons',
+          {
+            onclick: () => {
+              if (allWasChecked) {
+                globals.visibleFlowCategories.clear();
+              } else {
+                categoryToFlowsNum.forEach((_, cat) => {
+                  globals.visibleFlowCategories.set(cat, true);
+                });
+              }
+              globals.visibleFlowCategories.set(ALL_CATEGORIES, !allWasChecked);
+              globals.rafScheduler.scheduleFullRedraw();
+            },
+          },
+          allWasChecked ? CHECKBOX : BLANK_CHECKBOX))
+    ]));
+
+    categoryToFlowsNum.forEach((num, cat) => {
+      const wasChecked = globals.visibleFlowCategories.get(cat) ||
+          globals.visibleFlowCategories.get(ALL_CATEGORIES);
+      const data = [
+        m('td.flow-info', cat),
+        m('td.flow-info', num),
+        m('td.flow-info',
+          m('i.material-icons',
+            {
+              onclick: () => {
+                if (wasChecked) {
+                  globals.visibleFlowCategories.set(ALL_CATEGORIES, false);
+                }
+                globals.visibleFlowCategories.set(cat, !wasChecked);
+                globals.rafScheduler.scheduleFullRedraw();
+              },
+            },
+            wasChecked ? CHECKBOX : BLANK_CHECKBOX))
+      ];
+      rows.push(m('tr', data));
+    });
+
+    return m('.details-panel', [
+      m('.details-panel-heading', m('h2', `Selected flow events`)),
+      m('.flow-events-table', m('table.half-width', rows)),
+    ]);
+  }
+
+  renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {}
+}
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
new file mode 100644
index 0000000..6d75244
--- /dev/null
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -0,0 +1,309 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size 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 {TRACK_SHELL_WIDTH} from './css_constants';
+import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
+import {Flow, FlowPoint, globals} from './globals';
+import {PanelVNode} from './panel';
+import {findUiTrackId} from './scroll_helper';
+import {SliceRect} from './track';
+import {TrackGroupPanel} from './track_group_panel';
+import {TrackPanel} from './track_panel';
+
+const TRACK_GROUP_CONNECTION_OFFSET = 5;
+const TRIANGLE_SIZE = 5;
+const CIRCLE_RADIUS = 3;
+const BEZIER_OFFSET = 30;
+
+const CONNECTED_FLOW_HUE = 10;
+const SELECTED_FLOW_HUE = 230;
+
+const DEFAULT_FLOW_WIDTH = 2;
+const FOCUSED_FLOW_WIDTH = 3;
+
+const HIGHLIGHTED_FLOW_INTENSITY = 45;
+const FOCUSED_FLOW_INTENSITY = 55;
+const DEFAULT_FLOW_INTENSITY = 70;
+
+type LineDirection = 'LEFT'|'RIGHT'|'UP'|'DOWN';
+type ConnectionType = 'TRACK'|'TRACK_GROUP';
+
+interface TrackPanelInfo {
+  panel: TrackPanel;
+  yStart: number;
+}
+
+interface TrackGroupPanelInfo {
+  panel: TrackGroupPanel;
+  yStart: number;
+  height: number;
+}
+
+function HasTrackId(obj: {}): obj is {trackId: number} {
+  return (obj as {trackId?: number}).trackId !== undefined;
+}
+
+function HasId(obj: {}): obj is {id: number} {
+  return (obj as {id?: number}).id !== undefined;
+}
+
+function HasTrackGroupId(obj: {}): obj is {trackGroupId: string} {
+  return (obj as {trackGroupId?: string}).trackGroupId !== undefined;
+}
+
+export class FlowEventsRendererArgs {
+  trackIdToTrackPanel: Map<number, TrackPanelInfo>;
+  groupIdToTrackGroupPanel: Map<string, TrackGroupPanelInfo>;
+
+  constructor(public canvasWidth: number, public canvasHeight: number) {
+    this.trackIdToTrackPanel = new Map<number, TrackPanelInfo>();
+    this.groupIdToTrackGroupPanel = new Map<string, TrackGroupPanelInfo>();
+  }
+
+  registerPanel(panel: PanelVNode, yStart: number, height: number) {
+    if (panel.state instanceof TrackPanel && HasId(panel.attrs)) {
+      const config = globals.state.tracks[panel.attrs.id].config;
+      if (HasTrackId(config)) {
+        this.trackIdToTrackPanel.set(
+            config.trackId, {panel: panel.state, yStart});
+      }
+    } else if (
+        panel.state instanceof TrackGroupPanel &&
+        HasTrackGroupId(panel.attrs)) {
+      this.groupIdToTrackGroupPanel.set(
+          panel.attrs.trackGroupId, {panel: panel.state, yStart, height});
+    }
+  }
+}
+
+export class FlowEventsRenderer {
+  private getTrackGroupIdByTrackId(trackId: number): string|undefined {
+    const uiTrackId = findUiTrackId(trackId);
+    return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined;
+  }
+
+  private getTrackGroupYCoordinate(
+      args: FlowEventsRendererArgs, trackId: number): number|undefined {
+    const trackGroupId = this.getTrackGroupIdByTrackId(trackId);
+    if (!trackGroupId) {
+      return undefined;
+    }
+    const trackGroupInfo = args.groupIdToTrackGroupPanel.get(trackGroupId);
+    if (!trackGroupInfo) {
+      return undefined;
+    }
+    return trackGroupInfo.yStart + trackGroupInfo.height -
+        TRACK_GROUP_CONNECTION_OFFSET;
+  }
+
+  private getTrackYCoordinate(args: FlowEventsRendererArgs, trackId: number):
+      number|undefined {
+    return args.trackIdToTrackPanel.get(trackId) ?.yStart;
+  }
+
+  private getYConnection(
+      args: FlowEventsRendererArgs, trackId: number,
+      rect?: SliceRect): {y: number, connection: ConnectionType}|undefined {
+    if (!rect) {
+      const y = this.getTrackGroupYCoordinate(args, trackId);
+      if (y === undefined) {
+        return undefined;
+      }
+      return {y, connection: 'TRACK_GROUP'};
+    }
+    const y = (this.getTrackYCoordinate(args, trackId) || 0) + rect.top +
+        rect.height * 0.5;
+
+    return {
+      y: Math.min(Math.max(0, y), args.canvasHeight),
+      connection: 'TRACK'
+    };
+  }
+
+  private getXCoordinate(ts: number): number {
+    return globals.frontendLocalState.timeScale.timeToPx(ts);
+  }
+
+  private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint):
+      SliceRect|undefined {
+    const trackPanel = args.trackIdToTrackPanel.get(point.trackId) ?.panel;
+    if (!trackPanel) {
+      return undefined;
+    }
+    return trackPanel.getSliceRect(
+        point.sliceStartTs, point.sliceEndTs, point.depth);
+  }
+
+  render(ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs) {
+    ctx.save();
+    ctx.translate(TRACK_SHELL_WIDTH, 0);
+    ctx.rect(0, 0, args.canvasWidth - TRACK_SHELL_WIDTH, args.canvasHeight);
+    ctx.clip();
+
+    globals.connectedFlows.forEach(flow => {
+      this.drawFlow(ctx, args, flow, CONNECTED_FLOW_HUE);
+    });
+
+    globals.selectedFlows.forEach(flow => {
+      const categories = getFlowCategories(flow);
+      for (const cat of categories) {
+        if (globals.visibleFlowCategories.get(cat) ||
+            globals.visibleFlowCategories.get(ALL_CATEGORIES)) {
+          this.drawFlow(ctx, args, flow, SELECTED_FLOW_HUE);
+          break;
+        }
+      }
+    });
+
+    ctx.restore();
+  }
+
+  private drawFlow(
+      ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs, flow: Flow,
+      hue: number) {
+    const beginSliceRect = this.getSliceRect(args, flow.begin);
+    const endSliceRect = this.getSliceRect(args, flow.end);
+
+    const beginYConnection =
+        this.getYConnection(args, flow.begin.trackId, beginSliceRect);
+    const endYConnection =
+        this.getYConnection(args, flow.end.trackId, endSliceRect);
+
+    if (!beginYConnection || !endYConnection) {
+      return;
+    }
+
+    let beginDir: LineDirection = 'LEFT';
+    let endDir: LineDirection = 'RIGHT';
+    if (beginYConnection.connection === 'TRACK_GROUP') {
+      beginDir = beginYConnection.y > endYConnection.y ? 'DOWN' : 'UP';
+    }
+    if (endYConnection.connection === 'TRACK_GROUP') {
+      endDir = endYConnection.y > beginYConnection.y ? 'DOWN' : 'UP';
+    }
+
+    const begin = {
+      x: this.getXCoordinate(flow.begin.sliceEndTs),
+      y: beginYConnection.y,
+      dir: beginDir
+    };
+    const end = {
+      x: this.getXCoordinate(flow.end.sliceStartTs),
+      y: endYConnection.y,
+      dir: endDir
+    };
+    const highlighted =
+        flow.end.sliceId === globals.frontendLocalState.highlightedSliceId ||
+        flow.begin.sliceId === globals.frontendLocalState.highlightedSliceId;
+    const focused = flow.id === globals.frontendLocalState.focusedFlowIdLeft ||
+        flow.id === globals.frontendLocalState.focusedFlowIdRight;
+
+    let intensity = DEFAULT_FLOW_INTENSITY;
+    let width = DEFAULT_FLOW_WIDTH;
+    if (focused) {
+      intensity = FOCUSED_FLOW_INTENSITY;
+      width = FOCUSED_FLOW_WIDTH;
+    }
+    if (highlighted) {
+      intensity = HIGHLIGHTED_FLOW_INTENSITY;
+    }
+    this.drawFlowArrow(ctx, begin, end, hue, intensity, width);
+  }
+
+  private getDeltaX(dir: LineDirection, offset: number): number {
+    switch (dir) {
+      case 'LEFT':
+        return -offset;
+      case 'RIGHT':
+        return offset;
+      case 'UP':
+        return 0;
+      case 'DOWN':
+        return 0;
+      default:
+        return 0;
+    }
+  }
+
+  private getDeltaY(dir: LineDirection, offset: number): number {
+    switch (dir) {
+      case 'LEFT':
+        return 0;
+      case 'RIGHT':
+        return 0;
+      case 'UP':
+        return -offset;
+      case 'DOWN':
+        return offset;
+      default:
+        return 0;
+    }
+  }
+
+  private drawFlowArrow(
+      ctx: CanvasRenderingContext2D,
+      begin: {x: number, y: number, dir: LineDirection},
+      end: {x: number, y: number, dir: LineDirection}, hue: number,
+      intensity: number, width: number) {
+    const END_OFFSET =
+        (end.dir === 'RIGHT' || end.dir === 'LEFT' ? TRIANGLE_SIZE : 0);
+    const color = `hsl(${hue}, 50%, ${intensity}%)`;
+    // draw curved line from begin to end (bezier curve)
+    ctx.strokeStyle = color;
+    ctx.lineWidth = width;
+    ctx.beginPath();
+    ctx.moveTo(begin.x, begin.y);
+    ctx.bezierCurveTo(
+        begin.x - this.getDeltaX(begin.dir, BEZIER_OFFSET),
+        begin.y - this.getDeltaY(begin.dir, BEZIER_OFFSET),
+        end.x - this.getDeltaX(end.dir, BEZIER_OFFSET + END_OFFSET),
+        end.y - this.getDeltaY(end.dir, BEZIER_OFFSET + END_OFFSET),
+        end.x - this.getDeltaX(end.dir, END_OFFSET),
+        end.y - this.getDeltaY(end.dir, END_OFFSET));
+    ctx.stroke();
+
+    // TODO (andrewbb): probably we should add a parameter 'MarkerType' to be
+    // able to choose what marker we want to draw _before_ the function call.
+    // e.g. triangle, circle, square?
+    if (begin.dir !== 'RIGHT' && begin.dir !== 'LEFT') {
+      // draw a circle if we the line has a vertical connection
+      ctx.fillStyle = color;
+      ctx.beginPath();
+      ctx.arc(begin.x, begin.y, 3, 0, 2 * Math.PI);
+      ctx.closePath();
+      ctx.fill();
+    }
+
+
+    if (end.dir !== 'RIGHT' && end.dir !== 'LEFT') {
+      // draw a circle if we the line has a vertical connection
+      ctx.fillStyle = color;
+      ctx.beginPath();
+      ctx.arc(end.x, end.y, CIRCLE_RADIUS, 0, 2 * Math.PI);
+      ctx.closePath();
+      ctx.fill();
+    } else {
+      const dx = this.getDeltaX(end.dir, TRIANGLE_SIZE);
+      const dy = this.getDeltaY(end.dir, TRIANGLE_SIZE);
+      // draw small triangle
+      ctx.fillStyle = color;
+      ctx.beginPath();
+      ctx.moveTo(end.x, end.y);
+      ctx.lineTo(end.x - dx - dy, end.y + dx - dy);
+      ctx.lineTo(end.x - dx + dy, end.y - dx - dy);
+      ctx.closePath();
+      ctx.fill();
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 1583f9c..7826753 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -15,15 +15,14 @@
 import {Actions} from '../common/actions';
 import {HttpRpcState} from '../common/http_rpc_engine';
 import {
+  Area,
   FrontendLocalState as FrontendState,
   OmniboxState,
   Timestamped,
-  TimestampedAreaSelection,
   VisibleState,
 } from '../common/state';
 import {TimeSpan} from '../common/time';
 
-import {randomColor} from './colorizer';
 import {globals} from './globals';
 import {debounce, ratelimit} from './rate_limiters';
 import {TimeScale} from './time_scale';
@@ -70,10 +69,14 @@
   hoveredPid = -1;
   hoveredLogsTimestamp = -1;
   hoveredNoteTimestamp = -1;
+  highlightedSliceId = -1;
+  focusedFlowIdLeft = -1;
+  focusedFlowIdRight = -1;
   vidTimestamp = -1;
   localOnlyMode = false;
   sidebarVisible = true;
   showPanningHint = false;
+  showCookieConsent = false;
   visibleTracks = new Set<string>();
   prevVisibleTracks = new Set<string>();
   searchIndex = -1;
@@ -84,8 +87,6 @@
 
   // This is used to calculate the tracks within a Y range for area selection.
   areaY: Range = {};
-  // True if the user is in the process of doing an area selection.
-  selectingArea = false;
 
   private scrollBarWidth?: number;
 
@@ -102,9 +103,7 @@
     resolution: 1,
   };
 
-  private _selectedArea: TimestampedAreaSelection = {
-    lastUpdate: 0,
-  };
+  private _selectedArea?: Area;
 
   // TODO: there is some redundancy in the fact that both |visibleWindowTime|
   // and a |timeScale| have a notion of time range. That should live in one
@@ -128,6 +127,21 @@
     globals.rafScheduler.scheduleRedraw();
   }
 
+  setHighlightedSliceId(sliceId: number) {
+    this.highlightedSliceId = sliceId;
+    globals.rafScheduler.scheduleRedraw();
+  }
+
+  setHighlightedFlowLeftId(flowId: number) {
+    this.focusedFlowIdLeft = flowId;
+    globals.rafScheduler.scheduleFullRedraw();
+  }
+
+  setHighlightedFlowRightId(flowId: number) {
+    this.focusedFlowIdRight = flowId;
+    globals.rafScheduler.scheduleFullRedraw();
+  }
+
   // Sets the timestamp at which a vertical line will be drawn.
   setHoveredLogsTimestamp(ts: number) {
     if (this.hoveredLogsTimestamp === ts) return;
@@ -168,7 +182,6 @@
 
   // Called when beginning a canvas redraw.
   clearVisibleTracks() {
-    this.prevVisibleTracks = new Set(this.visibleTracks);
     this.visibleTracks.clear();
   }
 
@@ -179,100 +192,33 @@
             value => this.visibleTracks.has(value))) {
       globals.dispatch(
           Actions.setVisibleTracks({tracks: Array.from(this.visibleTracks)}));
+      this.prevVisibleTracks = new Set(this.visibleTracks);
     }
   }
 
   mergeState(state: FrontendState): void {
     this._omniboxState = chooseLatest(this._omniboxState, state.omniboxState);
     this._visibleState = chooseLatest(this._visibleState, state.visibleState);
-    this._selectedArea = chooseLatest(this._selectedArea, state.selectedArea);
     if (this._visibleState === state.visibleState) {
       this.updateLocalTime(
           new TimeSpan(this._visibleState.startSec, this._visibleState.endSec));
     }
   }
 
-  private selectAreaDebounced = debounce(() => {
-    globals.dispatch(Actions.selectArea(this._selectedArea));
-  }, 20);
-
   selectArea(
       startSec: number, endSec: number,
-      tracks = this._selectedArea.area ? this._selectedArea.area.tracks : []) {
-    if (this.currentNoteSelectionEqualToCurrentAreaSelection()) {
-      globals.dispatch(Actions.deselect({}));
-    }
+      tracks = this._selectedArea ? this._selectedArea.tracks : []) {
     this.showPanningHint = true;
-    this._selectedArea = {
-      area: {startSec, endSec, tracks},
-      lastUpdate: Date.now() / 1000
-    };
-    this.selectAreaDebounced();
-    globals.rafScheduler.scheduleFullRedraw();
-  }
-
-  toggleTrackSelection(id: string, isTrackGroup = false) {
-    const area = this._selectedArea.area;
-    if (!area) return;
-    const index = area.tracks.indexOf(id);
-    if (index > -1) {
-      area.tracks.splice(index, 1);
-      if (isTrackGroup) {  // Also remove all child tracks.
-        for (const childTrack of globals.state.trackGroups[id].tracks) {
-          const childIndex = area.tracks.indexOf(childTrack);
-          if (childIndex > -1) {
-            area.tracks.splice(childIndex, 1);
-          }
-        }
-      }
-    } else {
-      area.tracks.push(id);
-      if (isTrackGroup) {  // Also add all child tracks.
-        for (const childTrack of globals.state.trackGroups[id].tracks) {
-          if (!area.tracks.includes(childTrack)) {
-            area.tracks.push(childTrack);
-          }
-        }
-      }
-    }
-    this._selectedArea.lastUpdate = Date.now() / 1000;
-    this.selectAreaDebounced();
-    globals.rafScheduler.scheduleFullRedraw();
-  }
-
-  toggleLockArea() {
-    if (!this._selectedArea.area) return;
-    if (this.currentNoteSelectionEqualToCurrentAreaSelection()) {
-      if (globals.state.currentSelection != null &&
-          globals.state.currentSelection.kind === 'NOTE') {
-        globals.dispatch(
-            Actions.removeNote({id: globals.state.currentSelection.id}));
-      }
-    } else {
-      const color = randomColor();
-      globals.dispatch(Actions.addAreaNote({
-        timestamp: this._selectedArea.area.startSec,
-        area: this._selectedArea.area,
-        color
-      }));
-    }
-
+    this._selectedArea = {startSec, endSec, tracks},
     globals.rafScheduler.scheduleFullRedraw();
   }
 
   deselectArea() {
-    // When an area is deselected (and it is marked) also deselect the current
-    // marked selection if it is for the same area.
-    if (this.currentNoteSelectionEqualToCurrentAreaSelection()) {
-      globals.dispatch(Actions.deselect({}));
-    }
-    this._selectedArea = {lastUpdate: Date.now() / 1000};
-    this.selectAreaDebounced();
-    globals.frontendLocalState.currentTab = undefined;
-    globals.rafScheduler.scheduleFullRedraw();
+    this._selectedArea = undefined;
+    globals.rafScheduler.scheduleRedraw();
   }
 
-  get selectedArea(): TimestampedAreaSelection {
+  get selectedArea(): Area|undefined {
     return this._selectedArea;
   }
 
@@ -310,26 +256,6 @@
     this.ratelimitedUpdateVisible();
   }
 
-  // We lock an area selection by adding an area note. When we select the note
-  // it will also select the area but then the user can select other things,
-  // like a slice or different note and the area note will be deselected even
-  // though the area selection remains. So it is useful to know if we currently
-  // have the same area note selected and area selection.
-  private currentNoteSelectionEqualToCurrentAreaSelection() {
-    if (!this._selectedArea.area) return false;
-    if (globals.state.currentSelection != null &&
-        globals.state.currentSelection.kind === 'NOTE') {
-      const curNote = globals.state.notes[globals.state.currentSelection.id];
-      // TODO(taylori): Do the tracks need to be the same too?
-      if (curNote.noteType === 'AREA' &&
-          curNote.area.startSec === this._selectedArea.area.startSec &&
-          curNote.area.endSec === this._selectedArea.area.endSec) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   updateVisibleTime(ts: TimeSpan) {
     this.updateLocalTime(ts);
     this._visibleState.lastUpdate = Date.now() / 1000;
@@ -342,6 +268,11 @@
   // Whenever start/end px of the timeScale is changed, update
   // the resolution.
   updateLocalLimits(pxStart: number, pxEnd: number) {
+    // Numbers received here can be negative or equal, but we should fix that
+    // before updating the timescale.
+    pxStart = Math.max(0, pxStart);
+    pxEnd = Math.max(0, pxEnd);
+    if (pxStart === pxEnd) pxEnd = pxStart + 1;
     this.timeScale.setLimitsPx(pxStart, pxEnd);
     this.updateResolution();
   }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 331302b..2962bf5 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -15,9 +15,11 @@
 import {assertExists} from '../base/logging';
 import {DeferredAction} from '../common/actions';
 import {AggregateData} from '../common/aggregation_data';
+import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 import {CallsiteInfo, createEmptyState, State} from '../common/state';
 import {fromNs, toNs} from '../common/time';
+import {Analytics, initAnalytics} from '../frontend/analytics';
 
 import {FrontendLocalState} from './frontend_local_state';
 import {RafScheduler} from './raf_scheduler';
@@ -37,6 +39,7 @@
   endState?: string;
   cpu?: number;
   id?: number;
+  threadStateId?: number;
   utid?: number;
   wakeupTs?: number;
   wakerUtid?: number;
@@ -47,6 +50,28 @@
   description?: Description;
 }
 
+export interface FlowPoint {
+  trackId: number;
+
+  sliceName: string;
+  sliceCategory: string;
+  sliceId: number;
+  sliceStartTs: number;
+  sliceEndTs: number;
+
+  depth: number;
+}
+
+export interface Flow {
+  id: number;
+
+  begin: FlowPoint;
+  end: FlowPoint;
+
+  category?: string;
+  name?: string;
+}
+
 export interface CounterDetails {
   startTime?: number;
   value?: number;
@@ -54,6 +79,15 @@
   duration?: number;
 }
 
+export interface ThreadStateDetails {
+  ts?: number;
+  dur?: number;
+  state?: string;
+  utid?: number;
+  cpu?: number;
+  sliceId?: number;
+}
+
 export interface HeapProfileDetails {
   type?: string;
   id?: number;
@@ -101,6 +135,7 @@
   private _frontendLocalState?: FrontendLocalState = undefined;
   private _rafScheduler?: RafScheduler = undefined;
   private _serviceWorkerController?: ServiceWorkerController = undefined;
+  private _logging?: Analytics = undefined;
 
   // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
   private _trackDataStore?: TrackDataStore = undefined;
@@ -109,6 +144,10 @@
   private _aggregateDataStore?: AggregateDataStore = undefined;
   private _threadMap?: ThreadMap = undefined;
   private _sliceDetails?: SliceDetails = undefined;
+  private _threadStateDetails?: ThreadStateDetails = undefined;
+  private _connectedFlows?: Flow[] = undefined;
+  private _selectedFlows?: Flow[] = undefined;
+  private _visibleFlowCategories?: Map<string, boolean> = undefined;
   private _counterDetails?: CounterDetails = undefined;
   private _heapProfileDetails?: HeapProfileDetails = undefined;
   private _cpuProfileDetails?: CpuProfileDetails = undefined;
@@ -116,11 +155,13 @@
   private _bufferUsage?: number = undefined;
   private _recordingLog?: string = undefined;
   private _traceErrors?: number = undefined;
+  private _metricError?: string = undefined;
+  private _metricResult?: MetricResult = undefined;
 
   private _currentSearchResults: CurrentSearchResults = {
-    sliceIds: new Float64Array(0),
-    tsStarts: new Float64Array(0),
-    utids: new Float64Array(0),
+    sliceIds: [],
+    tsStarts: [],
+    utids: [],
     trackIds: [],
     sources: [],
     totalResults: 0,
@@ -143,6 +184,7 @@
     this._frontendLocalState = new FrontendLocalState();
     this._rafScheduler = new RafScheduler();
     this._serviceWorkerController = new ServiceWorkerController();
+    this._logging = initAnalytics();
 
     // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
     this._trackDataStore = new Map<string, {}>();
@@ -151,7 +193,11 @@
     this._aggregateDataStore = new Map<string, AggregateData>();
     this._threadMap = new Map<number, ThreadDesc>();
     this._sliceDetails = {};
+    this._connectedFlows = [];
+    this._selectedFlows = [];
+    this._visibleFlowCategories = new Map<string, boolean>();
     this._counterDetails = {};
+    this._threadStateDetails = {};
     this._heapProfileDetails = {};
     this._cpuProfileDetails = {};
   }
@@ -176,6 +222,10 @@
     return assertExists(this._rafScheduler);
   }
 
+  get logging() {
+    return assertExists(this._logging);
+  }
+
   get serviceWorkerController() {
     return assertExists(this._serviceWorkerController);
   }
@@ -205,6 +255,38 @@
     this._sliceDetails = assertExists(click);
   }
 
+  get threadStateDetails() {
+    return assertExists(this._threadStateDetails);
+  }
+
+  set threadStateDetails(click: ThreadStateDetails) {
+    this._threadStateDetails = assertExists(click);
+  }
+
+  get connectedFlows() {
+    return assertExists(this._connectedFlows);
+  }
+
+  set connectedFlows(connectedFlows: Flow[]) {
+    this._connectedFlows = assertExists(connectedFlows);
+  }
+
+  get selectedFlows() {
+    return assertExists(this._selectedFlows);
+  }
+
+  set selectedFlows(selectedFlows: Flow[]) {
+    this._selectedFlows = assertExists(selectedFlows);
+  }
+
+  get visibleFlowCategories() {
+    return assertExists(this._visibleFlowCategories);
+  }
+
+  set visibleFlowCategories(visibleFlowCategories: Map<string, boolean>) {
+    this._visibleFlowCategories = assertExists(visibleFlowCategories);
+  }
+
   get counterDetails() {
     return assertExists(this._counterDetails);
   }
@@ -233,6 +315,22 @@
     this._traceErrors = arg;
   }
 
+  get metricError() {
+    return this._metricError;
+  }
+
+  setMetricError(arg: string) {
+    this._metricError = arg;
+  }
+
+  get metricResult() {
+    return this._metricResult;
+  }
+
+  setMetricResult(result: MetricResult) {
+    this._metricResult = result;
+  }
+
   get cpuProfileDetails() {
     return assertExists(this._cpuProfileDetails);
   }
@@ -293,14 +391,15 @@
     // 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);
-    return fromNs(Math.pow(2, Math.floor(Math.log2(toNs(pxToSec)))));
+    const pxToNs = Math.max(toNs(pxToSec), 1);
+    return fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs))));
   }
 
-  makeSelection(action: DeferredAction<{}>) {
+  makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
     // A new selection should cancel the current search selection.
     globals.frontendLocalState.searchIndex = -1;
     globals.frontendLocalState.currentTab =
-        action.type === 'deselect' ? undefined : 'current_selection';
+        action.type === 'deselect' ? undefined : tabToOpen;
     globals.dispatch(action);
   }
 
@@ -317,12 +416,14 @@
     this._overviewStore = undefined;
     this._threadMap = undefined;
     this._sliceDetails = undefined;
+    this._threadStateDetails = undefined;
     this._aggregateDataStore = undefined;
     this._numQueriesQueued = 0;
+    this._metricResult = undefined;
     this._currentSearchResults = {
-      sliceIds: new Float64Array(0),
-      tsStarts: new Float64Array(0),
-      utids: new Float64Array(0),
+      sliceIds: [],
+      tsStarts: [],
+      utids: [],
       trackIds: [],
       sources: [],
       totalResults: 0,
diff --git a/ui/src/frontend/help_modal.ts b/ui/src/frontend/help_modal.ts
index 114438d..a562f47 100644
--- a/ui/src/frontend/help_modal.ts
+++ b/ui/src/frontend/help_modal.ts
@@ -15,6 +15,7 @@
 
 import * as m from 'mithril';
 
+import {globals} from './globals';
 import {hideModel, showModal} from './modal';
 
 let helpModelOpen = false;
@@ -23,6 +24,7 @@
   if (helpModelOpen) {
     hideHelp();
   } else {
+    globals.logging.logEvent('User Actions', 'Show help');
     showHelp();
   }
 }
@@ -64,8 +66,31 @@
               m('td', keycap('f'), ' (with event selected)'),
               m('td', 'Scroll + zoom to current selection')),
             m('tr',
+              m('td', keycap('['), '/', keycap(']'), ' (with event selected)'),
+              m('td',
+                'Select next/previous slice that is connected by a flow.',
+                m('br'),
+                'If there are multiple flows,' +
+                    'the one that is in focus (bold) is selected')),
+            m('tr',
+              m('td',
+                keycap('Ctrl'),
+                ' + ',
+                keycap('['),
+                '/',
+                keycap(']'),
+                ' (with event selected)'),
+              m('td', 'Switch focus to another flow')),
+            m('tr',
               m('td', keycap('m'), ' (with event or area selected)'),
-              m('td', 'Mark the area')),
+              m('td', 'Mark the area (temporarily)')),
+            m('tr',
+              m('td',
+                keycap('Shift'),
+                ' + ',
+                keycap('m'),
+                ' (with event or area selected)'),
+              m('td', 'Mark the area (persistently)')),
             m('tr', m('td', keycap('?')), m('td', 'Show help')),
             )),
     buttons: [],
diff --git a/ui/src/frontend/home_page.ts b/ui/src/frontend/home_page.ts
index 721f0bb..5d37cbb 100644
--- a/ui/src/frontend/home_page.ts
+++ b/ui/src/frontend/home_page.ts
@@ -21,6 +21,9 @@
     return m(
         '.page.home-page',
         m('.home-page-title', 'Perfetto'),
-        m('img.logo[src=assets/logo-3d.png]'), );
-  },
+        m('img.logo[src=assets/logo-3d.png]'),
+        m('a.privacy',
+          {href: 'https://policies.google.com/privacy', target: '_blank'},
+          'Privacy policy'));
+  }
 });
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 5f235c8..d4cb2da 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -28,25 +28,31 @@
   LogExists,
   LogExistsKey
 } from '../common/logs';
+import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 
 import {AnalyzePage} from './analyze_page';
+import {loadAndroidBugToolInfo} from './android_bug_tool';
 import {maybeShowErrorDialog} from './error_dialog';
 import {
   CounterDetails,
   CpuProfileDetails,
+  Flow,
   globals,
   HeapProfileDetails,
   QuantizedLoad,
   SliceDetails,
-  ThreadDesc
+  ThreadDesc,
+  ThreadStateDetails
 } from './globals';
 import {HomePage} from './home_page';
 import {openBufferWithLegacyTraceViewer} from './legacy_trace_viewer';
+import {MetricsPage} from './metrics_page';
 import {postMessageHandler} from './post_message_handler';
 import {RecordPage, updateAvailableAdbDevices} from './record_page';
 import {Router} from './router';
 import {CheckHttpRpcConnection} from './rpc_http_dialog';
+import {taskTracker} from './task_tracker';
 import {TraceInfoPage} from './trace_info_page';
 import {ViewerPage} from './viewer_page';
 
@@ -123,6 +129,43 @@
     this.redraw();
   }
 
+  publishThreadStateDetails(click: ThreadStateDetails) {
+    globals.threadStateDetails = click;
+    this.redraw();
+  }
+
+  publishConnectedFlows(connectedFlows: Flow[]) {
+    globals.connectedFlows = connectedFlows;
+    // Call resetFlowFocus() each time connectedFlows is updated to correctly
+    // navigate using hotkeys.
+    this.resetFlowFocus();
+    this.redraw();
+  }
+
+  // If a chrome slice is selected and we have any flows in connectedFlows
+  // we will find the flows on the right and left of that slice to set a default
+  // focus. In all other cases the focusedFlowId(Left|Right) will be set to -1.
+  resetFlowFocus() {
+    globals.frontendLocalState.focusedFlowIdLeft = -1;
+    globals.frontendLocalState.focusedFlowIdRight = -1;
+    if (globals.state.currentSelection?.kind === 'CHROME_SLICE') {
+      const sliceId = globals.state.currentSelection.id;
+      for (const flow of globals.connectedFlows) {
+        if (flow.begin.sliceId === sliceId) {
+          globals.frontendLocalState.focusedFlowIdRight = flow.id;
+        }
+        if (flow.end.sliceId === sliceId) {
+          globals.frontendLocalState.focusedFlowIdLeft = flow.id;
+        }
+      }
+    }
+  }
+
+  publishSelectedFlows(selectedFlows: Flow[]) {
+    globals.selectedFlows = selectedFlows;
+    this.redraw();
+  }
+
   publishCounterDetails(click: CounterDetails) {
     globals.counterDetails = click;
     this.redraw();
@@ -188,6 +231,17 @@
     this.redraw();
   }
 
+  publishMetricError(error: string) {
+    globals.setMetricError(error);
+    globals.logging.logError(error, false);
+    this.redraw();
+  }
+
+  publishMetricResult(metricResult: MetricResult) {
+    globals.setMetricResult(metricResult);
+    this.redraw();
+  }
+
   publishAggregateData(args: {data: AggregateData, kind: string}) {
     globals.setAggregateData(args.kind, args.data);
     this.redraw();
@@ -209,20 +263,6 @@
   }));
 }
 
-function fetchChromeTracingCategoriesFromExtension(
-    extensionPort: chrome.runtime.Port) {
-  extensionPort.postMessage({method: 'GetCategories'});
-}
-
-function onExtensionMessage(message: object) {
-  const typedObject = message as {type: string};
-  if (typedObject.type === 'GetCategoriesResponse') {
-    const categoriesMessage = message as {categories: string[]};
-    globals.dispatch(Actions.setChromeCategories(
-        {categories: categoriesMessage.categories}));
-  }
-}
-
 function main() {
   // Add Error handlers for JS error and for uncaught exceptions in promises.
   setErrorHandler((err: string) => maybeShowErrorDialog(err));
@@ -254,6 +294,9 @@
 
   const dispatch =
       controllerChannel.port2.postMessage.bind(controllerChannel.port2);
+  globals.initialize(dispatch, controller);
+  globals.serviceWorkerController.install();
+
   const router = new Router(
       '/',
       {
@@ -261,12 +304,12 @@
         '/viewer': ViewerPage,
         '/record': RecordPage,
         '/query': AnalyzePage,
+        '/metrics': MetricsPage,
         '/info': TraceInfoPage,
       },
-      dispatch);
+      dispatch,
+      globals.logging);
   forwardRemoteCalls(frontendChannel.port2, new FrontendApi(router));
-  globals.initialize(dispatch, controller);
-  globals.serviceWorkerController.install();
 
   // We proxy messages between the extension and the controller because the
   // controller's worker can't access chrome.runtime.
@@ -285,10 +328,8 @@
     // This forwards the messages from the extension to the controller.
     extensionPort.onMessage.addListener(
         (message: object, _port: chrome.runtime.Port) => {
-          onExtensionMessage(message);
           extensionLocalChannel.port2.postMessage(message);
         });
-    fetchChromeTracingCategoriesFromExtension(extensionPort);
   }
 
   updateAvailableAdbDevices();
@@ -320,14 +361,32 @@
   // /?s=xxxx for permalinks.
   const stateHash = Router.param('s');
   const urlHash = Router.param('url');
-  if (stateHash) {
+  const androidBugTool = Router.param('openFromAndroidBugTool');
+  if (typeof stateHash === 'string' && stateHash) {
     globals.dispatch(Actions.loadPermalink({
       hash: stateHash,
     }));
-  } else if (urlHash) {
+  } else if (typeof urlHash === 'string' && urlHash) {
     globals.dispatch(Actions.openTraceFromUrl({
       url: urlHash,
     }));
+  } else if (androidBugTool) {
+    // TODO(hjd): Unify updateStatus and TaskTracker
+    globals.dispatch(Actions.updateStatus({
+      msg: 'Loading trace from ABT extension',
+      timestamp: Date.now() / 1000
+    }));
+    const loadInfo = loadAndroidBugToolInfo();
+    taskTracker.trackPromise(loadInfo, 'Loading trace from ABT extension');
+    loadInfo
+        .then(info => {
+          globals.dispatch(Actions.openTraceFromFile({
+            file: info.file,
+          }));
+        })
+        .catch(e => {
+          console.error(e);
+        });
   }
 
   // Prevent pinch zoom.
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 5b08351..064994d 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -13,26 +13,29 @@
 // limitations under the License.
 
 import {Actions} from '../common/actions';
+import {Area} from '../common/state';
 
-import {globals} from './globals';
+import {Flow, globals} from './globals';
 import {toggleHelp} from './help_modal';
 import {
+  findUiTrackId,
   horizontalScrollAndZoomToRange,
   verticalScrollToTrack
 } from './scroll_helper';
 import {executeSearch} from './search_handler';
 
+type Direction = 'Forward'|'Backward';
+
 // Handles all key events than are not handled by the
 // pan and zoom handler.
 export function handleKey(e: KeyboardEvent, down: boolean) {
   const key = e.key.toLowerCase();
+  const selection = globals.state.currentSelection;
   if (down && 'm' === key) {
-    const selectedArea = globals.frontendLocalState.selectedArea.area;
-    if (!selectedArea && globals.state.currentSelection !== null) {
-      selectSliceSpan();
-    }
-    if (selectedArea) {
-      globals.frontendLocalState.toggleLockArea();
+    if (selection && selection.kind === 'AREA') {
+      globals.dispatch(Actions.toggleMarkCurrentArea({persistent: e.shiftKey}));
+    } else if (selection) {
+      lockSliceSpan(e.shiftKey);
     }
   }
   if (down && 'f' === key) {
@@ -62,6 +65,103 @@
     e.preventDefault();
     executeSearch(e.shiftKey);
   }
+  if (down && 'escape' === key) {
+    globals.frontendLocalState.deselectArea();
+    globals.makeSelection(Actions.deselect({}));
+    globals.dispatch(Actions.removeNote({id: '0'}));
+  }
+  if (down && ']' === key) {
+    if (e.ctrlKey) {
+      focusOtherFlow('Forward');
+    } else {
+      moveByFocusedFlow('Forward');
+    }
+  }
+  if (down && '[' === key) {
+    if (e.ctrlKey) {
+      focusOtherFlow('Backward');
+    } else {
+      moveByFocusedFlow('Backward');
+    }
+  }
+}
+
+// Search |boundFlows| for |flowId| and return the id following it.
+// Returns the first flow id if nothing was found or |flowId| was the last flow
+// in |boundFlows|, and -1 if |boundFlows| is empty
+function findAnotherFlowExcept(boundFlows: Flow[], flowId: number): number {
+  let selectedFlowFound = false;
+
+  if (boundFlows.length === 0) {
+    return -1;
+  }
+
+  for (const flow of boundFlows) {
+    if (selectedFlowFound) {
+      return flow.id;
+    }
+
+    if (flow.id === flowId) {
+      selectedFlowFound = true;
+    }
+  }
+  return boundFlows[0].id;
+}
+
+// Change focus to the next flow event (matching the direction)
+function focusOtherFlow(direction: Direction) {
+  if (!globals.state.currentSelection ||
+      globals.state.currentSelection.kind !== 'CHROME_SLICE') {
+    return;
+  }
+  const sliceId = globals.state.currentSelection.id;
+  if (sliceId === -1) {
+    return;
+  }
+
+  const boundFlows = globals.connectedFlows.filter(
+      flow => flow.begin.sliceId === sliceId && direction === 'Forward' ||
+          flow.end.sliceId === sliceId && direction === 'Backward');
+
+  if (direction === 'Backward') {
+    const nextFlowId = findAnotherFlowExcept(
+        boundFlows, globals.frontendLocalState.focusedFlowIdLeft);
+    globals.frontendLocalState.setHighlightedFlowLeftId(nextFlowId);
+  } else {
+    const nextFlowId = findAnotherFlowExcept(
+        boundFlows, globals.frontendLocalState.focusedFlowIdRight);
+    globals.frontendLocalState.setHighlightedFlowRightId(nextFlowId);
+  }
+}
+
+// Select the slice connected to the flow in focus
+function moveByFocusedFlow(direction: Direction) {
+  if (!globals.state.currentSelection ||
+      globals.state.currentSelection.kind !== 'CHROME_SLICE') {
+    return;
+  }
+
+  const sliceId = globals.state.currentSelection.id;
+  const flowId =
+      (direction === 'Backward' ?
+           globals.frontendLocalState.focusedFlowIdLeft :
+           globals.frontendLocalState.focusedFlowIdRight);
+
+  if (sliceId === -1 || flowId === -1) {
+    return;
+  }
+
+  // Find flow that is in focus and select corresponding slice
+  for (const flow of globals.connectedFlows) {
+    if (flow.id === flowId) {
+      const flowPoint = (direction === 'Backward' ? flow.begin : flow.end);
+      const uiTrackId = findUiTrackId(flowPoint.trackId);
+      if (uiTrackId) {
+        globals.makeSelection(Actions.selectChromeSlice(
+            {id: flowPoint.sliceId, trackId: uiTrackId, table: 'slice'}));
+      }
+    }
+  }
 }
 
 function findTimeRangeOfSelection() {
@@ -76,8 +176,11 @@
         endTs = startTs + slice.dur;
       }
     } else if (selection.kind === 'THREAD_STATE') {
-      startTs = selection.ts;
-      endTs = startTs + selection.dur;
+      const threadState = globals.threadStateDetails;
+      if (threadState.ts && threadState.dur) {
+        startTs = threadState.ts + globals.state.traceTime.startSec;
+        endTs = startTs + threadState.dur;
+      }
     } else if (selection.kind === 'COUNTER') {
       startTs = selection.leftTs;
       endTs = selection.rightTs;
@@ -86,14 +189,16 @@
   return {startTs, endTs};
 }
 
-function selectSliceSpan() {
+
+function lockSliceSpan(persistent = false) {
   const range = findTimeRangeOfSelection();
   if (range.startTs !== -1 && range.endTs !== -1 &&
-      globals.state.currentSelection) {
+      globals.state.currentSelection !== null) {
     const tracks = globals.state.currentSelection.trackId ?
         [globals.state.currentSelection.trackId] :
         [];
-    globals.frontendLocalState.selectArea(range.startTs, range.endTs, tracks);
+    const area: Area = {startSec: range.startTs, endSec: range.endTs, tracks};
+    globals.dispatch(Actions.markArea({area, persistent}));
   }
 }
 
diff --git a/ui/src/frontend/metrics_page.ts b/ui/src/frontend/metrics_page.ts
new file mode 100644
index 0000000..fa85032
--- /dev/null
+++ b/ui/src/frontend/metrics_page.ts
@@ -0,0 +1,82 @@
+// 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.
+
+import * as m from 'mithril';
+
+import {Actions} from '../common/actions';
+import {globals} from './globals';
+import {createPage} from './pages';
+
+function getCurrSelectedMetric() {
+  const {availableMetrics, selectedIndex} = globals.state.metrics;
+  if (!availableMetrics) return undefined;
+  if (selectedIndex === undefined) return undefined;
+  return availableMetrics[selectedIndex];
+}
+
+class MetricResult implements m.ClassComponent {
+  view() {
+    const metricResult = globals.metricResult;
+    if (metricResult === undefined) return undefined;
+    const currSelection = getCurrSelectedMetric();
+    if (!(metricResult && metricResult.name === currSelection)) {
+      return undefined;
+    }
+    if (metricResult.error !== undefined) {
+      return m('pre.metric-error', metricResult.error);
+    }
+    if (metricResult.resultString !== undefined) {
+      return m('pre', metricResult.resultString);
+    }
+    return undefined;
+  }
+}
+
+class MetricPicker implements m.ClassComponent {
+  view() {
+    const {availableMetrics, selectedIndex} = globals.state.metrics;
+    if (availableMetrics === undefined) return 'Loading metrics...';
+    if (availableMetrics.length === 0) return 'No metrics available';
+    if (selectedIndex === undefined) {
+      throw Error('Should not happen when avaibleMetrics is non-empty');
+    }
+
+    return m('div', [
+      'Select a metric:',
+      m('select',
+        {
+          selectedIndex: globals.state.metrics.selectedIndex,
+          onchange: (e: InputEvent) => {
+            globals.dispatch(Actions.setMetricSelectedIndex(
+                {index: (e.target as HTMLSelectElement).selectedIndex}));
+          },
+        },
+        availableMetrics.map(
+            metric => m('option', {value: metric, key: metric}, metric))),
+      m('button.metric-run-button',
+        {onclick: () => globals.dispatch(Actions.requestSelectedMetric({}))},
+        'Run'),
+    ]);
+  }
+}
+
+export const MetricsPage = createPage({
+  view() {
+    return m(
+        '.metrics-page',
+        m(MetricPicker),
+        m(MetricResult),
+    );
+  }
+});
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index a5b9102..7deea7d 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -15,10 +15,10 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
+import {randomColor} from '../common/colorizer';
 import {AreaNote, Note} from '../common/state';
 import {timeToString} from '../common/time';
 
-import {randomColor} from './colorizer';
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {PerfettoMouseEvent} from './events';
 import {globals} from './globals';
@@ -36,6 +36,14 @@
   return s.slice(0, Math.min(newlineIndex, s.length, 16));
 }
 
+function getStartTimestamp(note: Note|AreaNote) {
+  if (note.noteType === 'AREA') {
+    return globals.state.areas[note.areaId].startSec;
+  } else {
+    return note.timestamp;
+  }
+}
+
 export class NotesPanel extends Panel {
   hoveredX: null|number = null;
 
@@ -85,11 +93,12 @@
     ctx.font = '10px Helvetica';
 
     for (const note of Object.values(globals.state.notes)) {
-      const timestamp = note.timestamp;
+      const timestamp = getStartTimestamp(note);
       if ((note.noteType !== 'AREA' && !timeScale.timeInBounds(timestamp)) ||
           (note.noteType === 'AREA' &&
-           !timeScale.timeInBounds(note.area.endSec) &&
-           !timeScale.timeInBounds(note.area.startSec))) {
+           !timeScale.timeInBounds(globals.state.areas[note.areaId].endSec) &&
+           !timeScale.timeInBounds(
+               globals.state.areas[note.areaId].startSec))) {
         continue;
       }
       const currentIsHovered =
@@ -97,18 +106,19 @@
       if (currentIsHovered) aNoteIsHovered = true;
 
       const selection = globals.state.currentSelection;
-      const isSelected = selection !== null && selection.kind === 'NOTE' &&
-          selection.id === note.id;
+      const isSelected = selection !== null &&
+          ((selection.kind === 'NOTE' && selection.id === note.id) ||
+           (selection.kind === 'AREA' && selection.noteId === note.id));
       const x = timeScale.timeToPx(timestamp);
       const left = Math.floor(x + TRACK_SHELL_WIDTH);
 
       // Draw flag or marker.
       if (note.noteType === 'AREA') {
+        const area = globals.state.areas[note.areaId];
         this.drawAreaMarker(
             ctx,
             left,
-            Math.floor(
-                timeScale.timeToPx(note.area.endSec) + TRACK_SHELL_WIDTH),
+            Math.floor(timeScale.timeToPx(area.endSec) + TRACK_SHELL_WIDTH),
             note.color,
             isSelected);
       } else {
@@ -206,17 +216,20 @@
 
 
   private onClick(x: number, _: number, isMovie: boolean) {
+    if (x < 0) return;
     const timeScale = globals.frontendLocalState.timeScale;
     const timestamp = timeScale.pxToTime(x);
     for (const note of Object.values(globals.state.notes)) {
       if (this.hoveredX && this.mouseOverNote(this.hoveredX, note)) {
         if (note.noteType === 'MOVIE') {
           globals.frontendLocalState.setVidTimestamp(note.timestamp);
-        } else if (note.noteType === 'AREA') {
-          globals.frontendLocalState.selectArea(
-              note.area.startSec, note.area.endSec, note.area.tracks);
         }
-        globals.makeSelection(Actions.selectNote({id: note.id}));
+        if (note.noteType === 'AREA') {
+          globals.makeSelection(
+              Actions.reSelectArea({areaId: note.areaId, noteId: note.id}));
+        } else {
+          globals.makeSelection(Actions.selectNote({id: note.id}));
+        }
         return;
       }
     }
@@ -229,11 +242,12 @@
 
   private mouseOverNote(x: number, note: AreaNote|Note): boolean {
     const timeScale = globals.frontendLocalState.timeScale;
-    const noteX = timeScale.timeToPx(note.timestamp);
+    const noteX = timeScale.timeToPx(getStartTimestamp(note));
     if (note.noteType === 'AREA') {
+      const noteArea = globals.state.areas[note.areaId];
       return (noteX <= x && x < noteX + AREA_TRIANGLE_WIDTH) ||
-          (timeScale.timeToPx(note.area.endSec) > x &&
-           x > timeScale.timeToPx(note.area.endSec) - AREA_TRIANGLE_WIDTH);
+          (timeScale.timeToPx(noteArea.endSec) > x &&
+           x > timeScale.timeToPx(noteArea.endSec) - AREA_TRIANGLE_WIDTH);
     } else {
       const width = (note.noteType === 'MOVIE') ? MOVIE_WIDTH : FLAG_WIDTH;
       return noteX <= x && x < noteX + width;
@@ -248,7 +262,8 @@
 export class NotesEditorPanel extends Panel<NotesEditorPanelAttrs> {
   view({attrs}: m.CVnode<NotesEditorPanelAttrs>) {
     const note = globals.state.notes[attrs.id];
-    const startTime = note.timestamp - globals.state.traceTime.startSec;
+    const startTime =
+        getStartTimestamp(note) - globals.state.traceTime.startSec;
     return m(
         '.notes-editor-panel',
         m('.notes-editor-panel-heading-bar',
@@ -259,25 +274,23 @@
               e.stopImmediatePropagation();
             },
             value: note.text,
-            onchange: m.withAttr(
-                'value',
-                newText => {
-                  globals.dispatch(Actions.changeNoteText({
-                    id: attrs.id,
-                    newText,
-                  }));
-                }),
+            onchange: (e: InputEvent) => {
+              const newText = (e.target as HTMLInputElement).value;
+              globals.dispatch(Actions.changeNoteText({
+                id: attrs.id,
+                newText,
+              }));
+            },
           }),
           m('span.color-change', `Change color: `, m('input[type=color]', {
               value: note.color,
-              onchange: m.withAttr(
-                  'value',
-                  newColor => {
-                    globals.dispatch(Actions.changeNoteColor({
-                      id: attrs.id,
-                      newColor,
-                    }));
-                  }),
+              onchange: (e: Event) => {
+                const newColor = (e.target as HTMLInputElement).value;
+                globals.dispatch(Actions.changeNoteColor({
+                  id: attrs.id,
+                  newColor,
+                }));
+              },
             })),
           m('button',
             {
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index cfc6ee8..23507ee 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -15,9 +15,9 @@
 import * as m from 'mithril';
 
 import {assertExists} from '../base/logging';
+import {hueForCpu} from '../common/colorizer';
 import {TimeSpan, timeToString} from '../common/time';
 
-import {hueForCpu} from './colorizer';
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {DragGestureHandler} from './drag_gesture_handler';
 import {globals} from './globals';
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index 3f3e29f..c595420 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -15,6 +15,8 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
+
+import {CookieConsent} from './cookie_consent';
 import {globals} from './globals';
 import {Sidebar} from './sidebar';
 import {Topbar} from './topbar';
@@ -23,13 +25,14 @@
   const permalink = globals.state.permalink;
   if (!permalink.requestId || !permalink.hash) return null;
   const url = `${self.location.origin}/#!/?s=${permalink.hash}`;
+
   return m('.alert-permalink', [
     m('div', 'Permalink: ', m(`a[href=${url}]`, url)),
     m('button',
       {
         onclick: () => globals.dispatch(Actions.clearPermalink({})),
       },
-      m('i.material-icons', 'close')),
+      m('i.material-icons.disallow-selection', 'close')),
   ]);
 }
 
@@ -50,6 +53,7 @@
         m(Topbar),
         m(Alerts),
         m(component),
+        m(CookieConsent),
       ];
       if (globals.frontendLocalState.perfDebug) {
         children.push(m('.perf-stats'));
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index c5ee552..b5a814d 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -97,8 +97,7 @@
   private onSelection:
       (dragStartX: number, dragStartY: number, prevX: number, currentX: number,
        currentY: number, editing: boolean) => void;
-  private selectingStarted: () => void;
-  private selectingEnded: () => void;
+  private endSelection: (edit: boolean) => void;
 
   constructor({
     element,
@@ -107,8 +106,7 @@
     onZoomed,
     editSelection,
     onSelection,
-    selectingStarted,
-    selectingEnded
+    endSelection
   }: {
     element: HTMLElement,
     contentOffsetX: number,
@@ -118,8 +116,7 @@
     onSelection:
         (dragStartX: number, dragStartY: number, prevX: number,
          currentX: number, currentY: number, editing: boolean) => void,
-    selectingStarted: () => void,
-    selectingEnded: () => void,
+    endSelection: (edit: boolean) => void,
   }) {
     this.element = element;
     this.contentOffsetX = contentOffsetX;
@@ -127,8 +124,7 @@
     this.onZoomed = onZoomed;
     this.editSelection = editSelection;
     this.onSelection = onSelection;
-    this.selectingStarted = selectingStarted;
-    this.selectingEnded = selectingEnded;
+    this.endSelection = endSelection;
 
     document.body.addEventListener('keydown', this.boundOnKeyDown);
     document.body.addEventListener('keyup', this.boundOnKeyUp);
@@ -159,7 +155,6 @@
           if (edit) {
             this.element.style.cursor = EDITING_RANGE_CURSOR;
           } else if (!this.shiftDown) {
-            this.selectingStarted();
             this.element.style.cursor = DRAG_CURSOR;
           }
         },
@@ -168,7 +163,7 @@
           this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR;
           dragStartX = -1;
           dragStartY = -1;
-          this.selectingEnded();
+          this.endSelection(edit);
         });
   }
 
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index a6b7709..2131c10 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -13,11 +13,14 @@
 // limitations under the License.
 
 import * as m from 'mithril';
-import {TimestampedAreaSelection} from 'src/common/state';
 
 import {assertExists, assertTrue} from '../base/logging';
 
 import {TOPBAR_HEIGHT, TRACK_SHELL_WIDTH} from './css_constants';
+import {
+  FlowEventsRenderer,
+  FlowEventsRendererArgs
+} from './flow_events_renderer';
 import {globals} from './globals';
 import {isPanelVNode, Panel, PanelSize, PanelVNode} from './panel';
 import {
@@ -36,7 +39,7 @@
 
 // We need any here so we can accept vnodes with arbitrary attrs.
 // tslint:disable-next-line:no-any
-export type AnyAttrsVnode = m.Vnode<any, {}>;
+export type AnyAttrsVnode = m.Vnode<any, any>;
 
 export interface Attrs {
   panels: AnyAttrsVnode[];
@@ -60,7 +63,8 @@
   private panelPositions: PanelPosition[] = [];
   private totalPanelHeight = 0;
   private canvasHeight = 0;
-  private prevAreaSelection?: TimestampedAreaSelection;
+
+  private flowEventsRenderer: FlowEventsRenderer;
 
   private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
   private perfStats = {
@@ -103,12 +107,11 @@
     return panels;
   }
 
+  // This finds the tracks covered by the in-progress area selection. When
+  // editing areaY is not set, so this will not be used.
   handleAreaSelection() {
-    const selection = globals.frontendLocalState.selectedArea;
-    const area = selection.area;
-    if ((this.prevAreaSelection &&
-         this.prevAreaSelection.lastUpdate >= selection.lastUpdate) ||
-        area === undefined ||
+    const area = globals.frontendLocalState.selectedArea;
+    if (area === undefined ||
         globals.frontendLocalState.areaY.start === undefined ||
         globals.frontendLocalState.areaY.end === undefined ||
         this.panelPositions.length === 0) {
@@ -153,7 +156,6 @@
       }
     }
     globals.frontendLocalState.selectArea(area.startSec, area.endSec, tracks);
-    this.prevAreaSelection = globals.frontendLocalState.selectedArea;
   }
 
   constructor(vnode: m.CVnode<Attrs>) {
@@ -161,6 +163,7 @@
     this.canvasRedrawer = () => this.redrawCanvas();
     globals.rafScheduler.addRedrawCallback(this.canvasRedrawer);
     perfDisplay.addContainer(this);
+    this.flowEventsRenderer = new FlowEventsRenderer();
   }
 
   oncreate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -215,7 +218,9 @@
   view({attrs}: m.CVnode<Attrs>) {
     this.attrs = attrs;
     const renderPanel = (panel: m.Vnode) => perfDebug() ?
-        m('.panel', panel, m('.debug-panel-border')) :
+        m('.panel',
+          {key: panel.key},
+          [panel, m('.debug-panel-border', {key: 'debug-panel-border'})]) :
         m('.panel', {key: panel.key}, panel);
 
     return [
@@ -330,24 +335,27 @@
     const panels = assertExists(this.attrs).panels;
     assertTrue(panels.length === this.panelPositions.length);
     let totalOnCanvas = 0;
+    const flowEventsRendererArgs =
+        new FlowEventsRendererArgs(this.parentWidth, this.canvasHeight);
     for (let i = 0; i < panels.length; i++) {
       const panel = panels[i];
       const panelHeight = this.panelPositions[i].height;
       const yStartOnCanvas = panelYStart - canvasYStart;
 
-      if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
-        panelYStart += panelHeight;
-        continue;
-      }
-
-      totalOnCanvas++;
-
       if (!isPanelVNode(panel)) {
         throw new Error('Vnode passed to panel container is not a panel');
       }
 
       // TODO(hjd): This cast should be unnecessary given the type guard above.
       const p = panel as PanelVNode<{}>;
+      flowEventsRendererArgs.registerPanel(p, yStartOnCanvas, panelHeight);
+
+      if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
+        panelYStart += panelHeight;
+        continue;
+      }
+
+      totalOnCanvas++;
 
       this.ctx.save();
       this.ctx.translate(0, yStartOnCanvas);
@@ -364,6 +372,8 @@
     }
 
     this.drawTopLayerOnCanvas();
+    this.flowEventsRenderer.render(this.ctx, flowEventsRendererArgs);
+    // Collect performance as the last thing we do.
     const redrawDur = debugNow() - redrawStart;
     this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
   }
@@ -372,12 +382,10 @@
   // the whole canvas rather than per panel.
   private drawTopLayerOnCanvas() {
     if (!this.ctx) return;
-    const selection = globals.frontendLocalState.selectedArea;
-    const area = selection.area;
+    const area = globals.frontendLocalState.selectedArea;
     if (area === undefined ||
         globals.frontendLocalState.areaY.start === undefined ||
-        globals.frontendLocalState.areaY.end === undefined ||
-        !globals.frontendLocalState.selectingArea) {
+        globals.frontendLocalState.areaY.end === undefined) {
       return;
     }
     if (this.panelPositions.length === 0 || area.tracks.length === 0) return;
diff --git a/ui/src/frontend/post_message_handler.ts b/ui/src/frontend/post_message_handler.ts
index 180fcb8..03efb23 100644
--- a/ui/src/frontend/post_message_handler.ts
+++ b/ui/src/frontend/post_message_handler.ts
@@ -123,7 +123,7 @@
 }
 
 function sanitizeString(str: string): string {
-  return str.replace(/[^A-Za-z0-9.\-_#:/ ]/g, ' ');
+  return str.replace(/[^A-Za-z0-9.\-_#:/?=&;% ]/g, ' ');
 }
 
 // tslint:disable:no-any
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index 078a70c..f69a8ba 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -24,6 +24,7 @@
 import {globals} from './globals';
 import {Panel} from './panel';
 import {
+  findUiTrackId,
   horizontalScrollAndZoomToRange,
   verticalScrollToTrack
 } from './scroll_helper';
@@ -42,16 +43,8 @@
     return true;
   }
 
-  static findUiTrackId(traceTrackId: number) {
-    for (const [uiTrackId, trackState] of Object.entries(
-             globals.state.tracks)) {
-      const config = trackState.config as {trackId: number};
-      if (config.trackId === traceTrackId) return uiTrackId;
-    }
-    return null;
-  }
-
-  static rowOnClickHandler(event: Event, row: Row) {
+  static rowOnClickHandler(
+      event: Event, row: Row, nextTab: 'CurrentSelection'|'QueryResults') {
     // TODO(dproy): Make click handler work from analyze page.
     if (globals.state.route !== '/viewer') return;
     // If the click bubbles up to the pan and zoom handler that will deselect
@@ -63,14 +56,22 @@
     const sliceDur = fromNs(Math.max(row.dur as number, 1));
     const sliceEnd = sliceStart + sliceDur;
     const trackId = row.track_id as number;
-    const uiTrackId = this.findUiTrackId(trackId);
+    const uiTrackId = findUiTrackId(trackId);
     if (uiTrackId === null) return;
     verticalScrollToTrack(uiTrackId, true);
     horizontalScrollAndZoomToRange(sliceStart, sliceEnd);
-    const sliceId = row.slice_id as number | undefined;
+    let sliceId: number|undefined;
+    if (row.type?.toString().includes('slice')) {
+      sliceId = row.id as number | undefined;
+    } else {
+      sliceId = row.slice_id as number | undefined;
+    }
     if (sliceId !== undefined) {
-      globals.makeSelection(Actions.selectChromeSlice(
-          {id: sliceId, trackId: uiTrackId, table: 'slice'}));
+      globals.makeSelection(
+          Actions.selectChromeSlice(
+              {id: sliceId, trackId: uiTrackId, table: 'slice'}),
+          nextTab === 'QueryResults' ? globals.frontendLocalState.currentTab :
+                                       'current_selection');
     }
   }
 
@@ -83,11 +84,21 @@
     const containsSliceLocation =
         QueryTableRow.columnsContainsSliceLocation(columns);
     const maybeOnClick = containsSliceLocation ?
-        (e: Event) => QueryTableRow.rowOnClickHandler(e, row) :
+        (e: Event) => QueryTableRow.rowOnClickHandler(e, row, 'QueryResults') :
+        null;
+    const maybeOnDblClick = containsSliceLocation ?
+        (e: Event) =>
+            QueryTableRow.rowOnClickHandler(e, row, 'CurrentSelection') :
         null;
     return m(
         'tr',
-        {onclick: maybeOnClick, 'clickable': containsSliceLocation},
+        {
+          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);
   }
 }
diff --git a/ui/src/frontend/record_config.ts b/ui/src/frontend/record_config.ts
new file mode 100644
index 0000000..029f6f0
--- /dev/null
+++ b/ui/src/frontend/record_config.ts
@@ -0,0 +1,137 @@
+// 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.
+
+import {RecordConfig} from '../common/state';
+import {validateRecordConfig} from '../controller/validate_config';
+
+const LOCAL_STORAGE_RECORD_CONFIGS_KEY = 'recordConfigs';
+
+class NamedRecordConfig {
+  title: string;
+  config: RecordConfig;
+  key: string;
+
+  constructor(title: string, config: RecordConfig, key: string) {
+    this.title = title;
+    this.config = this.validateData(config);
+    this.key = key;
+  }
+
+  private validateData(config: {}): RecordConfig {
+    const validConfig = validateRecordConfig(config);
+    if (validConfig.errorMessage) {
+      // TODO(bsebastien): Show a warning message to the user in the UI.
+      console.warn(validConfig.errorMessage);
+    }
+    return validConfig.config;
+  }
+
+  static isValid(jsonObject: object): jsonObject is NamedRecordConfig {
+    return (jsonObject as NamedRecordConfig).title !== undefined &&
+        (jsonObject as NamedRecordConfig).config !== undefined &&
+        (jsonObject as NamedRecordConfig).key !== undefined;
+  }
+}
+
+export class RecordConfigStore {
+  recordConfigs: NamedRecordConfig[];
+
+  constructor() {
+    this.recordConfigs = [];
+    this.reloadFromLocalStorage();
+  }
+
+  save(recordConfig: RecordConfig, title?: string): void {
+    // We reload from local storage in case of concurrent
+    // modifications of local storage from a different tab.
+    this.reloadFromLocalStorage();
+
+    const config = new NamedRecordConfig(
+        title ? title : new Date().toJSON(), recordConfig, new Date().toJSON());
+
+    this.recordConfigs.push(config);
+    window.localStorage.setItem(
+        LOCAL_STORAGE_RECORD_CONFIGS_KEY, JSON.stringify(this.recordConfigs));
+  }
+
+  delete(key: string): void {
+    // We reload from local storage in case of concurrent
+    // modifications of local storage from a different tab.
+    this.reloadFromLocalStorage();
+
+    let idx = -1;
+    for (let i = 0; i < this.recordConfigs.length; ++i) {
+      if (this.recordConfigs[i].key === key) {
+        idx = i;
+        break;
+      }
+    }
+
+    if (idx !== -1) {
+      this.recordConfigs.splice(idx, 1);
+      window.localStorage.setItem(
+          LOCAL_STORAGE_RECORD_CONFIGS_KEY, JSON.stringify(this.recordConfigs));
+    } else {
+      // TODO(bsebastien): Show a warning message to the user in the UI.
+      console.warn('The config selected doesn\'t exist any more');
+    }
+  }
+
+  private clearRecordConfigs(): void {
+    this.recordConfigs = [];
+    window.localStorage.setItem(
+        LOCAL_STORAGE_RECORD_CONFIGS_KEY, JSON.stringify([]));
+  }
+
+  reloadFromLocalStorage(): void {
+    const configsLocalStorage =
+        window.localStorage.getItem(LOCAL_STORAGE_RECORD_CONFIGS_KEY);
+
+    if (configsLocalStorage) {
+      try {
+        const validConfigLocalStorage: NamedRecordConfig[] = [];
+        const parsedConfigsLocalStorage = JSON.parse(configsLocalStorage);
+
+        // Check if it's an array.
+        if (!Array.isArray(parsedConfigsLocalStorage)) {
+          this.clearRecordConfigs();
+          return;
+        }
+
+        for (let i = 0; i < parsedConfigsLocalStorage.length; ++i) {
+          if (!NamedRecordConfig.isValid(parsedConfigsLocalStorage[i])) {
+            continue;
+          }
+          validConfigLocalStorage.push(new NamedRecordConfig(
+              parsedConfigsLocalStorage[i].title,
+              parsedConfigsLocalStorage[i].config,
+              parsedConfigsLocalStorage[i].key));
+        }
+
+        this.recordConfigs = validConfigLocalStorage;
+        window.localStorage.setItem(
+            LOCAL_STORAGE_RECORD_CONFIGS_KEY,
+            JSON.stringify(validConfigLocalStorage));
+      } catch (e) {
+        this.clearRecordConfigs();
+      }
+    } else {
+      this.clearRecordConfigs();
+    }
+  }
+}
+
+// This class is a singleton to avoid many instances
+// conflicting as they attempt to edit localStorage.
+export const recordConfigStore = new RecordConfigStore();
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index a1a4440..d10b7a1 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -26,6 +26,7 @@
   isAndroidP,
   isAndroidTarget,
   isChromeTarget,
+  isCrOSTarget,
   RecordingTarget
 } from '../common/state';
 import {MAX_TIME, RecordMode} from '../common/state';
@@ -33,6 +34,7 @@
 
 import {globals} from './globals';
 import {createPage} from './pages';
+import {recordConfigStore} from './record_config';
 import {
   CodeSnippet,
   Dropdown,
@@ -46,6 +48,8 @@
 } from './record_widgets';
 import {Router} from './router';
 
+const LOCAL_STORAGE_SHOW_CONFIG = 'showConfigs';
+
 const POLL_INTERVAL_MS = [250, 500, 1000, 2500, 5000, 30000, 60000];
 
 const ATRACE_CATEGORIES = new Map<string, string>();
@@ -107,6 +111,7 @@
 FTRACE_CATEGORIES.set('task/*', 'task');
 FTRACE_CATEGORIES.set('task/*', 'task');
 FTRACE_CATEGORIES.set('vmscan/*', 'vmscan');
+FTRACE_CATEGORIES.set('fastrpc/*', 'fastrpc');
 
 function RecSettings(cssClass: string) {
   const S = (x: number) => x * 1000;
@@ -118,15 +123,14 @@
   const recButton = (mode: RecordMode, title: string, img: string) => {
     const checkboxArgs = {
       checked: cfg.mode === mode,
-      onchange: m.withAttr(
-          'checked',
-          (checked: boolean) => {
-            if (!checked) return;
-            const traceCfg = produce(globals.state.recordConfig, draft => {
-              draft.mode = mode;
-            });
-            globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
-          })
+      onchange: (e: InputEvent) => {
+        const checked = (e.target as HTMLInputElement).checked;
+        if (!checked) return;
+        const traceCfg = produce(globals.state.recordConfig, draft => {
+          draft.mode = mode;
+        });
+        globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
+      },
     };
     return m(
         `label${cfg.mode === mode ? '.selected' : ''}`,
@@ -211,13 +215,23 @@
 }
 
 function GpuSettings(cssClass: string) {
-  return m(`.record-section${cssClass}`, m(Probe, {
-             title: 'GPU frequency',
-             img: 'rec_cpu_freq.png',
-             descr: 'Records gpu frequency via ftrace',
-             setEnabled: (cfg, val) => cfg.gpuFreq = val,
-             isEnabled: (cfg) => cfg.gpuFreq
-           } as ProbeAttrs));
+  return m(
+      `.record-section${cssClass}`,
+      m(Probe, {
+        title: 'GPU frequency',
+        img: 'rec_cpu_freq.png',
+        descr: 'Records gpu frequency via ftrace',
+        setEnabled: (cfg, val) => cfg.gpuFreq = val,
+        isEnabled: (cfg) => cfg.gpuFreq
+      } as ProbeAttrs),
+      m(Probe, {
+        title: 'GPU memory',
+        img: 'rec_gpu_mem_total.png',
+        descr: `Allows to track per process and global total GPU memory usages.
+                (Available on recent Android 12+ kernels)`,
+        setEnabled: (cfg, val) => cfg.gpuMemTotal = val,
+        isEnabled: (cfg) => cfg.gpuMemTotal
+      } as ProbeAttrs));
 }
 
 function CpuSettings(cssClass: string) {
@@ -255,15 +269,6 @@
         isEnabled: (cfg) => cfg.cpuFreq
       } as ProbeAttrs),
       m(Probe, {
-        title: 'Scheduling chains / latency analysis',
-        img: 'rec_cpu_wakeup.png',
-        descr: `Tracks causality of scheduling transitions. When a task
-                X transitions from blocked -> runnable, keeps track of the
-                task Y that X's transition (e.g. posting a semaphore).`,
-        setEnabled: (cfg, val) => cfg.cpuLatency = val,
-        isEnabled: (cfg) => cfg.cpuLatency
-      } as ProbeAttrs),
-      m(Probe, {
         title: 'Syscalls',
         img: null,
         descr: `Tracks the enter and exit of all syscalls.`,
@@ -308,8 +313,11 @@
       `.${cssClass}`,
       m(Textarea, {
         title: 'Names or pids of the processes to track',
+        docsLink:
+            'https://perfetto.dev/docs/data-sources/native-heap-profiler#heapprofd-targets',
         placeholder: 'One per line, e.g.:\n' +
             'system_server\n' +
+            'com.google.android.apps.photos\n' +
             '1503',
         set: (cfg, val) => cfg.hpProcesses = val,
         get: (cfg) => cfg.hpProcesses
@@ -580,6 +588,7 @@
         } as ProbeAttrs,
         m(Dropdown, {
           title: 'Buffers',
+          cssClass: '.multicolumn',
           options: LOG_BUFFERS,
           set: (cfg, val) => cfg.androidLogBuffers = val,
           get: (cfg) => cfg.androidLogBuffers
@@ -793,7 +802,9 @@
           m('select',
             {
               selectedIndex,
-              onchange: m.withAttr('value', onTargetChange),
+              onchange: (e: Event) => {
+                onTargetChange((e.target as HTMLSelectElement).value);
+              },
               onupdate: (select) => {
                 // Work around mithril bug
                 // (https://github.com/MithrilJS/mithril.js/issues/2107): We may
@@ -820,6 +831,11 @@
       globals.state.availableAdbDevices.find(d => d.serial === target) ||
       getDefaultRecordingTargets().find(t => t.os === target) ||
       getDefaultRecordingTargets()[0];
+
+  if (isChromeTarget(recordingTarget)) {
+    globals.dispatch(Actions.setUpdateChromeCategories({update: true}));
+  }
+
   globals.dispatch(Actions.setRecordingTarget({target: recordingTarget}));
   globals.rafScheduler.scheduleFullRedraw();
 }
@@ -827,13 +843,97 @@
 function Instructions(cssClass: string) {
   return m(
       `.record-section.instructions${cssClass}`,
-      m('header', 'Instructions'),
+      m('header', 'Trace command'),
+      localStorage.hasOwnProperty(LOCAL_STORAGE_SHOW_CONFIG) ?
+          m('button.permalinkconfig',
+            {
+              onclick: () => {
+                globals.dispatch(
+                    Actions.createPermalink({isRecordingConfig: true}));
+              },
+            },
+            'Share recording settings') :
+          null,
       RecordingSnippet(),
       BufferUsageProgressBar(),
       m('.buttons', StopCancelButtons()),
       recordingLog());
 }
 
+function displayRecordConfigs() {
+  return recordConfigStore.recordConfigs.map((item) => {
+    return m('.config', [
+      m('span.title-config', item.title),
+      m('button',
+        {
+          class: 'config-button load',
+          onclick: () => {
+            globals.dispatch(Actions.setRecordConfig({config: item.config}));
+            globals.rafScheduler.scheduleFullRedraw();
+          }
+        },
+        'load'),
+      m('button',
+        {
+          class: 'config-button delete',
+          onclick: () => {
+            recordConfigStore.delete(item.key);
+            globals.rafScheduler.scheduleFullRedraw();
+          }
+        },
+        'delete'),
+    ]);
+  });
+}
+
+function getSavedConfigList() {
+  if (recordConfigStore.recordConfigs.length === 0) {
+    return [];
+  }
+  return displayRecordConfigs();
+}
+
+export const ConfigTitleState = {
+  title: '',
+  getTitle: () => {
+    return ConfigTitleState.title;
+  },
+  setTitle: (value: string) => {
+    ConfigTitleState.title = value;
+  },
+  clearTitle: () => {
+    ConfigTitleState.title = '';
+  }
+};
+
+function Configurations(cssClass: string) {
+  return m(
+      `.record-section${cssClass}`,
+      m('header', 'Save and load configurations'),
+      m('.input-config',
+        [
+          m('input', {
+            value: ConfigTitleState.title,
+            placeholder: 'Title for config',
+            oninput() {
+              ConfigTitleState.setTitle(this.value);
+            }
+          }),
+          m('button',
+            {
+              class: 'config-button save',
+              onclick: () => {
+                recordConfigStore.save(
+                    globals.state.recordConfig, ConfigTitleState.getTitle());
+                globals.rafScheduler.scheduleFullRedraw();
+                ConfigTitleState.clearTitle();
+              }
+            },
+            'Save current config')
+        ]),
+      getSavedConfigList());
+}
+
 function BufferUsageProgressBar() {
   if (!globals.state.recordingInProgress) return [];
 
@@ -848,44 +948,59 @@
 }
 
 function RecordingNotes() {
-  const docUrl = '//docs.perfetto.dev/#/build-instructions?id=get-the-code';
+  const sideloadUrl =
+      'https://perfetto.dev/docs/contributing/build-instructions#get-the-code';
+  const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing';
+  const cmdlineUrl =
+      'https://perfetto.dev/docs/quickstart/android-tracing#perfetto-cmdline';
   const extensionURL = `https://chrome.google.com/webstore/detail/
       perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine`;
 
   const notes: m.Children = [];
-  const doc =
-      m('span', 'Follow the ', m('a', {href: docUrl}, 'instructions here.'));
 
   const msgFeatNotSupported =
-      m('div', `Some of the probes are only supported in the
-      last version of perfetto running on Android Q+`);
+      m('span', `Some probes are only supported in Perfetto versions running
+      on Android Q+. `);
 
   const msgPerfettoNotSupported =
-      m('div', `Perfetto is not supported natively before Android P.`);
-
-  const msgRecordingNotSupported =
-      m('div', `Recording Perfetto traces from the UI is not supported natively
-     before Android Q. If you are using a P device, please select 'Android P'
-     as the 'Target Platform' and collect the trace using ADB`);
+      m('span', `Perfetto is not supported natively before Android P. `);
 
   const msgSideload =
-      m('div',
-        `If you have a rooted device you can sideload the latest version of
-         perfetto. `,
-        doc);
+      m('span',
+        `If you have a rooted device you can `,
+        m('a',
+          {href: sideloadUrl, target: '_blank'},
+          `sideload the latest version of
+         Perfetto.`));
+
+  const msgRecordingNotSupported =
+      m('.note',
+        `Recording Perfetto traces from the UI is not supported natively
+     before Android Q. If you are using a P device, please select 'Android P'
+     as the 'Target Platform' and `,
+        m('a',
+          {href: cmdlineUrl, target: '_blank'},
+          `collect the trace using ADB.`));
 
   const msgChrome =
-      m('div',
+      m('.note',
         `To trace Chrome from the Perfetto UI, you need to install our `,
-        m('a', {href: extensionURL}, 'Chrome extension'),
+        m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
         ' and then reload this page.');
 
   const msgLinux =
-      m('div',
-        `In order to use perfetto on Linux you need to
-      compile it and run the following command from the build
-      output directory. `,
-        doc);
+      m('.note',
+        `Use this `,
+        m('a', {href: linuxUrl, target: '_blank'}, `quickstart guide`),
+        ` to get started with tracing on Linux.`);
+
+  const msgLongTraces = m(
+      '.note',
+      `Recording in long trace mode through the UI is not supported. Please copy
+    the command and `,
+      m('a',
+        {href: cmdlineUrl, target: '_blank'},
+        `collect the trace using ADB.`));
 
   if (isAdbTarget(globals.state.recordingTarget)) {
     notes.push(msgRecordingNotSupported);
@@ -894,12 +1009,10 @@
     case 'Q':
       break;
     case 'P':
-      notes.push(msgFeatNotSupported);
-      notes.push(msgSideload);
+      notes.push(m('.note', msgFeatNotSupported, msgSideload));
       break;
     case 'O':
-      notes.push(msgPerfettoNotSupported);
-      notes.push(msgSideload);
+      notes.push(m('.note', msgPerfettoNotSupported, msgSideload));
       break;
     case 'L':
       notes.push(msgLinux);
@@ -907,10 +1020,16 @@
     case 'C':
       if (!globals.state.extensionInstalled) notes.push(msgChrome);
       break;
+    case 'CrOS':
+      if (!globals.state.extensionInstalled) notes.push(msgChrome);
+      break;
     default:
   }
+  if (globals.state.recordConfig.mode === 'LONG_TRACE') {
+    notes.unshift(msgLongTraces);
+  }
 
-  return notes.length > 0 ? m('.note', notes) : [];
+  return notes.length > 0 ? m('div', notes) : [];
 }
 
 function RecordingSnippet() {
@@ -981,7 +1100,8 @@
   const buttons: m.Children = [];
 
   if (isAndroidTarget(target)) {
-    if (!recInProgress && isAdbTarget(target)) {
+    if (!recInProgress && isAdbTarget(target) &&
+        globals.state.recordConfig.mode !== 'LONG_TRACE') {
       buttons.push(start);
     }
   } else if (isChromeTarget(target) && state.extensionInstalled) {
@@ -1012,6 +1132,7 @@
 
   const target = globals.state.recordingTarget;
   if (isAndroidTarget(target) || isChromeTarget(target)) {
+    globals.logging.logEvent('Record Trace', `Record trace (${target.os})`);
     globals.dispatch(Actions.startRecording({}));
   }
 }
@@ -1146,42 +1267,55 @@
         m('a[href="#!/record?p=instructions"]',
           m(`li${routePage === 'instructions' ? '.active' : ''}`,
             m('i.material-icons.rec', 'fiber_manual_record'),
-            m('.title', 'Instructions'),
-            m('.sub', 'Generate config and instructions')))),
+            m('.title', 'Trace command'),
+            m('.sub', 'Manually record trace'))),
+        localStorage.hasOwnProperty(LOCAL_STORAGE_SHOW_CONFIG) ?
+            m('a[href="#!/record?p=config"]',
+              {
+                onclick: () => {
+                  recordConfigStore.reloadFromLocalStorage();
+                }
+              },
+              m(`li${routePage === 'config' ? '.active' : ''}`,
+                m('i.material-icons', 'tune'),
+                m('.title', 'Saved configs'),
+                m('.sub', 'Manage local configs'))) :
+            null),
       m('header', 'Probes'),
-      m('ul', isChromeTarget(target) ? [chromeProbe] : [
-        m('a[href="#!/record?p=cpu"]',
-          m(`li${routePage === 'cpu' ? '.active' : ''}`,
-            m('i.material-icons', 'subtitles'),
-            m('.title', 'CPU'),
-            m('.sub', 'CPU usage, scheduling, wakeups'))),
-        m('a[href="#!/record?p=gpu"]',
-          m(`li${routePage === 'gpu' ? '.active' : ''}`,
-            m('i.material-icons', 'aspect_ratio'),
-            m('.title', 'GPU'),
-            m('.sub', 'GPU frequency'))),
-        m('a[href="#!/record?p=power"]',
-          m(`li${routePage === 'power' ? '.active' : ''}`,
-            m('i.material-icons', 'battery_charging_full'),
-            m('.title', 'Power'),
-            m('.sub', 'Battery and other energy counters'))),
-        m('a[href="#!/record?p=memory"]',
-          m(`li${routePage === 'memory' ? '.active' : ''}`,
-            m('i.material-icons', 'memory'),
-            m('.title', 'Memory'),
-            m('.sub', 'Physical mem, VM, LMK'))),
-        m('a[href="#!/record?p=android"]',
-          m(`li${routePage === 'android' ? '.active' : ''}`,
-            m('i.material-icons', 'android'),
-            m('.title', 'Android apps & svcs'),
-            m('.sub', 'atrace and logcat'))),
-        chromeProbe,
-        m('a[href="#!/record?p=advanced"]',
-          m(`li${routePage === 'advanced' ? '.active' : ''}`,
-            m('i.material-icons', 'settings'),
-            m('.title', 'Advanced settings'),
-            m('.sub', 'Complicated stuff for wizards')))
-      ]));
+      m('ul',
+        isChromeTarget(target) && !isCrOSTarget(target) ? [chromeProbe] : [
+          m('a[href="#!/record?p=cpu"]',
+            m(`li${routePage === 'cpu' ? '.active' : ''}`,
+              m('i.material-icons', 'subtitles'),
+              m('.title', 'CPU'),
+              m('.sub', 'CPU usage, scheduling, wakeups'))),
+          m('a[href="#!/record?p=gpu"]',
+            m(`li${routePage === 'gpu' ? '.active' : ''}`,
+              m('i.material-icons', 'aspect_ratio'),
+              m('.title', 'GPU'),
+              m('.sub', 'GPU frequency, memory'))),
+          m('a[href="#!/record?p=power"]',
+            m(`li${routePage === 'power' ? '.active' : ''}`,
+              m('i.material-icons', 'battery_charging_full'),
+              m('.title', 'Power'),
+              m('.sub', 'Battery and other energy counters'))),
+          m('a[href="#!/record?p=memory"]',
+            m(`li${routePage === 'memory' ? '.active' : ''}`,
+              m('i.material-icons', 'memory'),
+              m('.title', 'Memory'),
+              m('.sub', 'Physical mem, VM, LMK'))),
+          m('a[href="#!/record?p=android"]',
+            m(`li${routePage === 'android' ? '.active' : ''}`,
+              m('i.material-icons', 'android'),
+              m('.title', 'Android apps & svcs'),
+              m('.sub', 'atrace and logcat'))),
+          chromeProbe,
+          m('a[href="#!/record?p=advanced"]',
+            m(`li${routePage === 'advanced' ? '.active' : ''}`,
+              m('i.material-icons', 'settings'),
+              m('.title', 'Advanced settings'),
+              m('.sub', 'Complicated stuff for wizards')))
+        ]));
 }
 
 
@@ -1190,6 +1324,7 @@
     const SECTIONS: {[property: string]: (cssClass: string) => m.Child} = {
       buffers: RecSettings,
       instructions: Instructions,
+      config: Configurations,
       cpu: CpuSettings,
       gpu: GpuSettings,
       power: PowerSettings,
@@ -1200,7 +1335,8 @@
     };
 
     const pages: m.Children = [];
-    let routePage = Router.param('p');
+    const routePageParam = Router.param('p');
+    let routePage = typeof routePageParam === 'string' ? routePageParam : '';
     if (!Object.keys(SECTIONS).includes(routePage)) {
       routePage = 'buffers';
     }
diff --git a/ui/src/frontend/record_widgets.ts b/ui/src/frontend/record_widgets.ts
index 20d5901..9558919 100644
--- a/ui/src/frontend/record_widgets.ts
+++ b/ui/src/frontend/record_widgets.ts
@@ -27,6 +27,24 @@
 declare type Getter<T> = (cfg: RecordConfig) => T;
 
 // +---------------------------------------------------------------------------+
+// | Docs link with 'i' in circle icon.                                        |
+// +---------------------------------------------------------------------------+
+
+interface DocsChipAttrs {
+  href: string;
+}
+
+class DocsChip implements m.ClassComponent<DocsChipAttrs> {
+  view({attrs}: m.CVnode<DocsChipAttrs>) {
+    return m(
+        'a.inline-chip',
+        {href: attrs.href, title: 'Open docs in new tab', target: '_blank'},
+        m('i.material-icons', 'info'),
+        ' Docs');
+  }
+}
+
+// +---------------------------------------------------------------------------+
 // | Probe: the rectangular box on the right-hand-side with a toggle box.      |
 // +---------------------------------------------------------------------------+
 
@@ -56,8 +74,12 @@
           onclick: () => onToggle(!enabled),
         }),
         m('label',
-          m(`input[type=checkbox]`,
-            {checked: enabled, oninput: m.withAttr('checked', onToggle)}),
+          m(`input[type=checkbox]`, {
+            checked: enabled,
+            oninput: (e: InputEvent) => {
+              onToggle((e.target as HTMLInputElement).checked);
+            },
+          }),
           m('span', attrs.title)),
         m('div', m('div', attrs.descr), m('.probe-config', children)));
   }
@@ -122,13 +144,17 @@
         type: 'text',
         pattern: '(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}',  // hh:mm:ss
         value: new Date(val).toISOString().substr(11, 8),
-        oninput: m.withAttr('value', v => this.onTimeValueChange(attrs, v))
+        oninput: (e: InputEvent) => {
+          this.onTimeValueChange(attrs, (e.target as HTMLInputElement).value);
+        },
       };
     } else {
       spinnerCfg = {
         type: 'number',
         value: val,
-        oninput: m.withAttr('value', v => this.onValueChange(attrs, v))
+        oninput: (e: InputEvent) => {
+          this.onTimeValueChange(attrs, (e.target as HTMLInputElement).value);
+        },
       };
     }
     return m(
@@ -138,7 +164,11 @@
         attrs.icon !== undefined ? m('i.material-icons', attrs.icon) : [],
         m(`input[id="${id}"][type=range][min=0][max=${maxIdx}][value=${idx}]
         ${disabled ? '[disabled]' : ''}`,
-          {oninput: m.withAttr('value', v => this.onSliderChange(attrs, v))}),
+          {
+            oninput: (e: InputEvent) => {
+              this.onSliderChange(attrs, +(e.target as HTMLInputElement).value);
+            },
+          }),
         m(`input.spinner[min=${min !== undefined ? min : 1}][for=${id}]`,
           spinnerCfg),
         m('.unit', attrs.unit));
@@ -213,6 +243,7 @@
 
 export interface TextareaAttrs {
   placeholder: string;
+  docsLink?: string;
   cssClass?: string;
   get: Getter<string>;
   set: Setter<string>;
@@ -230,7 +261,9 @@
   view({attrs}: m.CVnode<TextareaAttrs>) {
     return m(
         '.textarea-holder',
-        m('header', attrs.title),
+        m('header',
+          attrs.title,
+          attrs.docsLink && [' ', m(DocsChip, {href: attrs.docsLink})]),
         m(`textarea.extra-input${attrs.cssClass || ''}`, {
           onchange: (e: Event) =>
               this.onChange(attrs, e.target as HTMLTextAreaElement),
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index ee82482..1c6a539 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -15,6 +15,7 @@
 import * as m from 'mithril';
 
 import {Actions, DeferredAction} from '../common/actions';
+import {Analytics} from '../frontend/analytics';
 
 interface RouteMap {
   [route: string]: m.Component;
@@ -25,7 +26,8 @@
 export class Router {
   constructor(
       private defaultRoute: string, private routes: RouteMap,
-      private dispatch: (a: DeferredAction) => void) {
+      private dispatch: (a: DeferredAction) => void,
+      private logging: Analytics) {
     if (!(defaultRoute in routes)) {
       throw Error('routes must define a component for defaultRoute.');
     }
@@ -53,6 +55,7 @@
    */
   setRouteOnHash(route: string) {
     history.pushState(undefined, "", ROUTE_PREFIX + route);
+    this.logging.updatePath(route);
 
     if (!(route in this.routes)) {
       console.info(
diff --git a/ui/src/frontend/router_jsdomtest.ts b/ui/src/frontend/router_jsdomtest.ts
index 52a140e..0d77e5b 100644
--- a/ui/src/frontend/router_jsdomtest.ts
+++ b/ui/src/frontend/router_jsdomtest.ts
@@ -16,6 +16,7 @@
 
 import {Actions, DeferredAction} from '../common/actions';
 
+import {NullAnalytics} from './analytics';
 import {Router} from './router';
 
 const mockComponent = {
@@ -24,23 +25,29 @@
 
 const fakeDispatch = () => {};
 
+const mockLogging = new NullAnalytics();
+
 beforeEach(() => {
   window.onhashchange = null;
   window.location.hash = '';
 });
 
 test('Default route must be defined', () => {
-  expect(() => new Router('/a', {'/b': mockComponent}, fakeDispatch)).toThrow();
+  expect(
+      () => new Router('/a', {'/b': mockComponent}, fakeDispatch, mockLogging))
+      .toThrow();
 });
 
 test('Resolves empty route to default component', () => {
-  const router = new Router('/a', {'/a': mockComponent}, fakeDispatch);
+  const router =
+      new Router('/a', {'/a': mockComponent}, fakeDispatch, mockLogging);
   expect(router.resolve('')).toBe(mockComponent);
   expect(router.resolve(null)).toBe(mockComponent);
 });
 
 test('Parse route from hash', () => {
-  const router = new Router('/', {'/': mockComponent}, fakeDispatch);
+  const router =
+      new Router('/', {'/': mockComponent}, fakeDispatch, mockLogging);
   window.location.hash = '#!/foobar?s=42';
   expect(router.getRouteFromHash()).toBe('/foobar');
 
@@ -56,7 +63,8 @@
         '/': mockComponent,
         '/a': mockComponent,
       },
-      dispatch);
+      dispatch,
+      mockLogging);
   const prevHistoryLength = window.history.length;
 
   router.setRouteOnHash('/a');
@@ -70,7 +78,7 @@
   const dispatch = dingus<(a: DeferredAction) => void>();
   // const dispatch = () => {console.log("action received")};
 
-  const router = new Router('/', {'/': mockComponent}, dispatch);
+  const router = new Router('/', {'/': mockComponent}, dispatch, mockLogging);
   router.setRouteOnHash('foo');
   expect(dispatch.calls.length).toBe(1);
   expect(dispatch.calls[0][1].length).toBeGreaterThanOrEqual(1);
@@ -88,7 +96,8 @@
         '/': mockComponent,
         '/viewer': mockComponent,
       },
-      mockDispatch);
+      mockDispatch,
+      mockLogging);
   window.location.hash = '#!/viewer';
 });
 
@@ -104,7 +113,8 @@
         '/': mockComponent,
         '/viewer': mockComponent,
       },
-      mockDispatch);
+      mockDispatch,
+      mockLogging);
 
   window.location.hash = '#invalid';
 });
@@ -112,8 +122,8 @@
 test('navigateToCurrentHash with valid current route', () => {
   const dispatch = dingus<(a: DeferredAction) => void>();
   window.location.hash = '#!/b';
-  const router =
-      new Router('/', {'/': mockComponent, '/b': mockComponent}, dispatch);
+  const router = new Router(
+      '/', {'/': mockComponent, '/b': mockComponent}, dispatch, mockLogging);
   router.navigateToCurrentHash();
   expect(dispatch.calls[0][1][0]).toEqual(Actions.navigate({route: '/b'}));
 });
@@ -121,7 +131,7 @@
 test('navigateToCurrentHash with invalid current route', () => {
   const dispatch = dingus<(a: DeferredAction) => void>();
   window.location.hash = '#!/invalid';
-  const router = new Router('/', {'/': mockComponent}, dispatch);
+  const router = new Router('/', {'/': mockComponent}, dispatch, mockLogging);
   router.navigateToCurrentHash();
   expect(dispatch.calls[0][1][0]).toEqual(Actions.navigate({route: '/'}));
 });
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index 37ee823..40e9036 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -26,12 +26,12 @@
 
 YES, use loaded trace:
 Will load from the current state of Trace Processor. If you did run
-trace_processor_shell --http file.pftrace this is likely what you want.
+trace_processor_shell --httpd file.pftrace this is likely what you want.
 
 YES, but reset state:
 Use this if you want to open another trace but still use the
 accelerator. This is the equivalent of killing and restarting
-trace_processor_shell --http.
+trace_processor_shell --httpd.
 
 NO, Use builtin WASM:
 Will not use the accelerator in this tab.
@@ -39,6 +39,8 @@
 Using the native accelerator has some minor caveats:
 - Only one tab can be using the accelerator.
 - Sharing, downloading and conversion-to-legacy aren't supported.
+- You may encounter UI errors if the Trace Processor version you are using is
+too old. Get the latest version from get.perfetto.dev/trace_processor.
 `;
 
 // Try to connect to the external Trace Processor HTTP RPC accelerator (if
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index 431e041..3e0e6d1 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -18,6 +18,8 @@
 
 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.
@@ -40,7 +42,11 @@
 export function horizontalScrollAndZoomToRange(startTs: number, endTs: number) {
   const visibleDur = globals.frontendLocalState.visibleWindowTime.end -
       globals.frontendLocalState.visibleWindowTime.start;
-  const selectDur = endTs - startTs;
+  let selectDur = endTs - startTs;
+  if (toNs(selectDur) === -1) {  // Unfinished slice
+    selectDur = INCOMPLETE_SLICE_TIME_S;
+    endTs = startTs;
+  }
   const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
   const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
   if (selectDur / visibleDur < 0.05 || startTs < viewStartNs ||
@@ -100,4 +106,17 @@
     verticalScrollToTrack(trackId, openGroup);
   }
   horizontalScrollToTs(ts);
+}
+
+/**
+ * Returns the UI track Id that is associated with the given |traceTrackId| in
+ * the trace_processor. Due to concepts like Async tracks and TrackGroups this
+ * is not always a one to one mapping.
+ */
+export function findUiTrackId(traceTrackId: number) {
+  for (const [uiTrackId, trackState] of Object.entries(globals.state.tracks)) {
+    const config = trackState.config as {trackId: number};
+    if (config.trackId === traceTrackId) return uiTrackId;
+  }
+  return null;
 }
\ No newline at end of file
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 65f46fe..091f54f 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -19,6 +19,15 @@
 import {globals} from './globals';
 import {scrollToTrackAndTs} from './scroll_helper';
 
+function setToPrevious(current: number) {
+  globals.frontendLocalState.setSearchIndex(Math.max(current - 1, 0));
+}
+
+function setToNext(current: number) {
+  globals.frontendLocalState.setSearchIndex(
+      Math.min(current + 1, globals.currentSearchResults.totalResults - 1));
+}
+
 export function executeSearch(reverse = false) {
   const state = globals.frontendLocalState;
   const index = state.searchIndex;
@@ -32,19 +41,22 @@
     if (reverse) {
       const [smaller,] =
         searchSegment(globals.currentSearchResults.tsStarts, endNs);
-      globals.frontendLocalState.setSearchIndex(smaller);
+      // If there is no item in the viewport just go to the previous.
+      smaller === -1 ? setToPrevious(index) :
+                       globals.frontendLocalState.setSearchIndex(smaller);
     } else {
       const [, larger] =
           searchSegment(globals.currentSearchResults.tsStarts, startNs);
-      globals.frontendLocalState.setSearchIndex(larger);
+      // If there is no item in the viewport just go to the next.
+      larger === -1 ? setToNext(index) :
+                      globals.frontendLocalState.setSearchIndex(larger);
     }
   } else {
     // If the currentTs is in the viewport, increment the index.
     if (reverse) {
-      globals.frontendLocalState.setSearchIndex(Math.max(index - 1, 0));
+      setToPrevious(index);
     } else {
-      globals.frontendLocalState.setSearchIndex(Math.min(
-          index + 1, globals.currentSearchResults.sliceIds.length - 1));
+      setToNext(index);
     }
   }
   selectCurrentSearchResult();
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 70ce5b4..606edde 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -14,12 +14,14 @@
 
 import * as m from 'mithril';
 
-import {assertTrue} from '../base/logging';
+import {assertExists, assertTrue} from '../base/logging';
 import {Actions} from '../common/actions';
 import {QueryResponse} from '../common/queries';
-import {EngineMode} from '../common/state';
+import {EngineMode, TraceArrayBufferSource} from '../common/state';
+import * as version from '../gen/perfetto_version';
 
 import {Animation} from './animation';
+import {copyToClipboard} from './clipboard';
 import {globals} from './globals';
 import {toggleHelp} from './help_modal';
 import {
@@ -27,25 +29,20 @@
   openFileWithLegacyTraceViewer,
 } from './legacy_trace_viewer';
 import {showModal} from './modal';
+import {isDownloadable, isShareable} from './trace_attrs';
 
 const ALL_PROCESSES_QUERY = 'select name, pid from process order by name;';
 
 const CPU_TIME_FOR_PROCESSES = `
 select
   process.name,
-  tot_proc/1e9 as cpu_sec
-from
-  (select
-    upid,
-    sum(tot_thd) as tot_proc
-  from
-    (select
-      utid,
-      sum(dur) as tot_thd
-    from sched group by utid)
-  join thread using(utid) group by upid)
+  sum(dur)/1e9 as cpu_sec
+from sched
+join thread using(utid)
 join process using(upid)
-order by cpu_sec desc limit 100;`;
+group by upid
+order by cpu_sec desc
+limit 100;`;
 
 const CYCLES_PER_P_STATE_PER_CPU = `
 select
@@ -64,26 +61,30 @@
 ) group by cpu, freq
 order by mcycles desc limit 32;`;
 
-const CPU_TIME_BY_CLUSTER_BY_PROCESS = `
-select process.name as process, thread, core, cpu_sec from (
-  select thread.name as thread, upid,
-    case when cpug = 0 then 'little' else 'big' end as core,
-    cpu_sec from (select cpu/4 as cpug, utid, sum(dur)/1e9 as cpu_sec
-    from sched group by utid, cpug order by cpu_sec desc
-  ) inner join thread using(utid)
-) inner join process using(upid) limit 30;`;
+const CPU_TIME_BY_CPU_BY_PROCESS = `
+select
+  process.name as process,
+  thread.name as thread,
+  cpu,
+  sum(dur) / 1e9 as cpu_sec
+from sched
+inner join thread using(utid)
+inner join process using(upid)
+group by utid, cpu
+order by cpu_sec desc
+limit 30;`;
 
 const HEAP_GRAPH_BYTES_PER_TYPE = `
 select
-  upid,
-  graph_sample_ts,
-  type_name,
-  sum(self_size) as total_self_size
-from heap_graph_object
+  o.upid,
+  o.graph_sample_ts,
+  c.name,
+  sum(o.self_size) as total_self_size
+from heap_graph_object o join heap_graph_class c on o.type_id = c.id
 group by
- upid,
- graph_sample_ts,
- type_name
+ o.upid,
+ o.graph_sample_ts,
+ c.name
 order by total_self_size desc
 limit 100;`;
 
@@ -111,6 +112,16 @@
   };
 }
 
+function showDebugTrack(): (_: Event) => void {
+  return (e: Event) => {
+    e.preventDefault();
+    globals.dispatch(Actions.addDebugTrack({
+      engineId: Object.keys(globals.state.engines)[0],
+      name: 'Debug Slices',
+    }));
+  };
+}
+
 const EXAMPLE_ANDROID_TRACE_URL =
     'https://storage.googleapis.com/perfetto-misc/example_android_trace_15s';
 
@@ -142,9 +153,8 @@
       {t: 'Show timeline', a: navigateViewer, i: 'line_style'},
       {
         t: 'Share',
-        a: dispatchCreatePermalink,
+        a: shareTrace,
         i: 'share',
-        checkDownloadDisabled: true,
         internalUserOnly: true,
       },
       {
@@ -155,6 +165,7 @@
       },
       {t: 'Legacy UI', a: openCurrentTraceWithOldUI, i: 'filter_none'},
       {t: 'Query (SQL)', a: navigateAnalyze, i: 'control_camera'},
+      {t: 'Metrics', a: navigateMetrics, i: 'speed'},
       {t: 'Info and stats', a: navigateInfo, i: 'info'},
     ],
   },
@@ -180,6 +191,11 @@
     summary: 'Compute summary statistics',
     items: [
       {
+        t: 'Show Debug Track',
+        a: showDebugTrack(),
+        i: 'view_day',
+      },
+      {
         t: 'All Processes',
         a: createCannedQuery(ALL_PROCESSES_QUERY),
         i: 'search',
@@ -195,8 +211,8 @@
         i: 'search',
       },
       {
-        t: 'CPU Time by cluster by process',
-        a: createCannedQuery(CPU_TIME_BY_CLUSTER_BY_PROCESS),
+        t: 'CPU Time by CPU by process',
+        a: createCannedQuery(CPU_TIME_BY_CPU_BY_PROCESS),
         i: 'search',
       },
       {
@@ -275,6 +291,7 @@
 function openCurrentTraceWithOldUI(e: Event) {
   e.preventDefault();
   console.assert(isTraceLoaded());
+  globals.logging.logEvent('Trace Actions', 'Open current trace in legacy UI');
   if (!isTraceLoaded) return;
   const engine = Object.values(globals.state.engines)[0];
   const src = engine.source;
@@ -282,13 +299,30 @@
     openInOldUIWithSizeCheck(new Blob([src.buffer]));
   } else if (src.type === 'FILE') {
     openInOldUIWithSizeCheck(src.file);
+  } else if (src.type === 'URL') {
+    m.request({
+       method: 'GET',
+       url: src.url,
+       // TODO(hjd): Once mithril is updated we can use responseType here rather
+       // than using config and remove the extract below.
+       config: xhr => {
+         xhr.responseType = 'blob';
+         xhr.onprogress = progress => {
+           const percent = (100 * progress.loaded / progress.total).toFixed(1);
+           globals.dispatch(Actions.updateStatus({
+             msg: `Downloading trace ${percent}%`,
+             timestamp: Date.now() / 1000,
+           }));
+         };
+       },
+       extract: xhr => {
+         return xhr.response;
+       }
+     }).then(result => {
+      openInOldUIWithSizeCheck(result as Blob);
+    });
   } else {
-    throw new Error('Loading from a URL to catapult is not yet supported');
-    // TODO(nicomazz): Find how to get the data of the current trace if it is
-    // from a URL. It seems that the trace downloaded is given to the trace
-    // processor, but not kept somewhere accessible. Maybe the only way is to
-    // download the trace (again), and then open it. An alternative can be to
-    // save a copy.
+    throw new Error(`Loading to catapult from source with type ${src.type}`);
   }
 }
 
@@ -306,6 +340,7 @@
 
 function openTraceUrl(url: string): (e: Event) => void {
   return e => {
+    globals.logging.logEvent('Trace Actions', 'Open example trace');
     e.preventDefault();
     globals.frontendLocalState.localOnlyMode = false;
     globals.dispatch(Actions.openTraceFromUrl({url}));
@@ -349,12 +384,13 @@
     globals.dispatch(Actions.openVideoFromFile({file}));
     return;
   }
-
+  globals.logging.logEvent('Trace Actions', 'Open trace from file');
   globals.dispatch(Actions.openTraceFromFile({file}));
 }
 
 async function openWithLegacyUi(file: File) {
   // Switch back to the old catapult UI.
+  globals.logging.logEvent('Trace Actions', 'Open trace in Legacy UI');
   if (await isLegacyTrace(file)) {
     openFileWithLegacyTraceViewer(file);
     return;
@@ -430,6 +466,11 @@
   globals.dispatch(Actions.navigate({route: '/query'}));
 }
 
+function navigateMetrics(e: Event) {
+  e.preventDefault();
+  globals.dispatch(Actions.navigate({route: '/metrics'}));
+}
+
 function navigateInfo(e: Event) {
   e.preventDefault();
   globals.dispatch(Actions.navigate({route: '/info'}));
@@ -440,26 +481,47 @@
   globals.dispatch(Actions.navigate({route: '/viewer'}));
 }
 
-function isDownloadAndShareDisabled(): boolean {
-  if (globals.frontendLocalState.localOnlyMode) return true;
-  const engine = Object.values(globals.state.engines)[0];
-  if (engine && engine.source.type === 'HTTP_RPC') return true;
-  return false;
-}
-
-function dispatchCreatePermalink(e: Event) {
+function shareTrace(e: Event) {
   e.preventDefault();
-  if (isDownloadAndShareDisabled() || !isTraceLoaded()) return;
+  const engine = assertExists(Object.values(globals.state.engines)[0]);
+  const traceUrl = (engine.source as (TraceArrayBufferSource)).url || '';
+
+  // If the trace is not shareable (has been pushed via postMessage()) but has
+  // a url, create a pseudo-permalink by echoing back the URL.
+  if (!isShareable()) {
+    const msg =
+        [m('p',
+           'This trace was opened by an external site and as such cannot ' +
+               'be re-shared preserving the UI state.')];
+    if (traceUrl) {
+      msg.push(m('p', 'By using the URL below you can open this trace again.'));
+      msg.push(m('p', 'Clicking will copy the URL into the clipboard.'));
+      msg.push(createTraceLink(traceUrl, traceUrl));
+    }
+
+    showModal({
+      title: 'Cannot create permalink from external trace',
+      content: m('div', msg),
+      buttons: []
+    });
+    return;
+  }
+
+  if (!isShareable() || !isTraceLoaded()) return;
 
   const result = confirm(
       `Upload the trace and generate a permalink. ` +
       `The trace will be accessible by anybody with the permalink.`);
-  if (result) globals.dispatch(Actions.createPermalink({}));
+  if (result) {
+    globals.logging.logEvent('Trace Actions', 'Create permalink');
+    globals.dispatch(Actions.createPermalink({isRecordingConfig: false}));
+  }
 }
 
 function downloadTrace(e: Event) {
   e.preventDefault();
-  if (!isTraceLoaded() || isDownloadAndShareDisabled()) return;
+  if (!isDownloadable() || !isTraceLoaded()) return;
+  globals.logging.logEvent('Trace Actions', 'Download trace');
 
   const engine = Object.values(globals.state.engines)[0];
   if (!engine) return;
@@ -483,6 +545,7 @@
   const a = document.createElement('a');
   a.href = url;
   a.download = fileName;
+  a.target = '_blank';
   document.body.appendChild(a);
   a.click();
   document.body.removeChild(a);
@@ -626,6 +689,16 @@
             'assessment')),
         m(EngineRPCWidget),
         m(ServiceWorkerWidget),
+        m(
+            '.version',
+            m('a',
+              {
+                href: `https://github.com/google/perfetto/tree/${
+                    version.SCM_REVISION}/ui`,
+                target: '_blank',
+              },
+              `${version.VERSION}`),
+            ),
     );
   }
 };
@@ -649,12 +722,11 @@
         if ((item as {internalUserOnly: boolean}).internalUserOnly === true) {
           if (!globals.isInternalUser) continue;
         }
-        if (isDownloadAndShareDisabled() &&
-            item.hasOwnProperty('checkDownloadDisabled')) {
+        if (!isDownloadable() && item.hasOwnProperty('checkDownloadDisabled')) {
           attrs = {
             onclick: e => {
               e.preventDefault();
-              alert('Can not download or share external trace.');
+              alert('Can not download external trace.');
             },
             href: '#',
             target: null,
@@ -668,6 +740,7 @@
         const engines = Object.values(globals.state.engines);
         if (engines.length === 1) {
           let traceTitle = '';
+          let traceUrl = '';
           switch (engines[0].source.type) {
             case 'FILE':
               // Split on both \ and / (because C:\Windows\paths\are\like\this).
@@ -676,10 +749,12 @@
               traceTitle += ` (${fileSizeMB} MB)`;
               break;
             case 'URL':
-              traceTitle = engines[0].source.url.split('/').pop()!;
+              traceUrl = engines[0].source.url;
+              traceTitle = traceUrl.split('/').pop()!;
               break;
             case 'ARRAY_BUFFER':
               traceTitle = engines[0].source.title;
+              traceUrl = engines[0].source.url || '';
               break;
             case 'HTTP_RPC':
               traceTitle = 'External trace (RPC)';
@@ -692,7 +767,7 @@
             if (tabTitle !== lastTabTitle) {
               document.title = lastTabTitle = tabTitle;
             }
-            vdomItems.unshift(m('li', m('a.trace-file-name', traceTitle)));
+            vdomItems.unshift(m('li', createTraceLink(traceTitle, traceUrl)));
           }
         }
       }
@@ -771,3 +846,20 @@
     );
   }
 }
+
+function createTraceLink(title: string, url: string) {
+  const linkProps = {
+    href: url,
+    title: url !== '' ? 'Click to copy the URL' : '',
+    target: '_blank',
+    onclick: (e: Event) => {
+      e.preventDefault();
+      copyToClipboard(url);
+      globals.dispatch(Actions.updateStatus({
+        msg: 'Link copied into the clipboard',
+        timestamp: Date.now() / 1000,
+      }));
+    },
+  };
+  return m('a.trace-file-name', linkProps, title);
+}
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 19ec09b..775c829 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -68,7 +68,7 @@
               m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
               m('tr',
                 m('th', `End State`),
-                m('td', `${translateState(sliceInfo.endState)}`))
+                m('td', translateState(sliceInfo.endState)))
             ]),
       );
     }
@@ -93,13 +93,9 @@
       }
     }
 
-    if (trackId) {
+    if (trackId && sliceInfo.threadStateId) {
       globals.makeSelection(Actions.selectThreadState({
-        utid: threadInfo.utid,
-        ts: sliceInfo.ts + globals.state.traceTime.startSec,
-        dur: sliceInfo.dur,
-        state: 'Running',
-        cpu: sliceInfo.cpu,
+        id: sliceInfo.threadStateId,
         trackId: trackId.toString(),
       }));
 
diff --git a/ui/src/frontend/task_tracker.ts b/ui/src/frontend/task_tracker.ts
new file mode 100644
index 0000000..7a43fd8
--- /dev/null
+++ b/ui/src/frontend/task_tracker.ts
@@ -0,0 +1,64 @@
+// 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.
+
+interface PromiseInfo {
+  startTimeMs: number;
+  message: string;
+}
+
+export class TaskTracker {
+  private promisesSeen: number;
+  private promisesRejected: number;
+  private promisesFulfilled: number;
+  private promiseInfo: Map<Promise<unknown>, PromiseInfo>;
+
+  constructor() {
+    this.promisesSeen = 0;
+    this.promisesRejected = 0;
+    this.promisesFulfilled = 0;
+    this.promiseInfo = new Map();
+  }
+
+  trackPromise(promise: Promise<unknown>, message: string): void {
+    this.promiseInfo.set(promise, {
+      startTimeMs: (new Date()).getMilliseconds(),
+      message,
+    });
+    this.promisesSeen += 1;
+    promise.then(() => {
+      this.promisesFulfilled += 1;
+    }).catch(() => {
+      this.promisesRejected += 1;
+    }).finally(() => {
+      this.promiseInfo.delete(promise);
+    });
+  }
+
+  hasPendingTasks(): boolean {
+    return this.promisesSeen > (this.promisesFulfilled + this.promisesRejected);
+  }
+
+  progressMessage(): string|undefined {
+    const {value} = this.promiseInfo.values().next();
+    if (value === undefined) {
+      return value;
+    } else {
+      const nowMs = (new Date()).getMilliseconds();
+      const runtimeSeconds = Math.round((nowMs - value.startTimeMs) / 1000);
+      return `${value.message} (${runtimeSeconds}s)`;
+    }
+  }
+}
+
+export const taskTracker = new TaskTracker();
diff --git a/ui/src/frontend/task_tracker_unittest.ts b/ui/src/frontend/task_tracker_unittest.ts
new file mode 100644
index 0000000..f869843
--- /dev/null
+++ b/ui/src/frontend/task_tracker_unittest.ts
@@ -0,0 +1,34 @@
+// 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.
+
+import {defer} from '../base/deferred';
+import {TaskTracker} from './task_tracker';
+
+test('it starts with no pending tasks', () => {
+  const tracker = new TaskTracker();
+  expect(tracker.hasPendingTasks()).toEqual(false);
+  expect(tracker.progressMessage()).toEqual(undefined);
+});
+
+test('it knows if a task is pending', () => {
+  const tracker = new TaskTracker();
+  const deferred = defer();
+
+  tracker.trackPromise(deferred, "Some task");
+
+  expect(tracker.hasPendingTasks()).toEqual(true);
+  expect(tracker.progressMessage()).toEqual("Some task (0s)");
+  deferred.resolve();
+});
+
diff --git a/ui/src/frontend/thread_state_panel.ts b/ui/src/frontend/thread_state_panel.ts
index d6257d4..ba58755 100644
--- a/ui/src/frontend/thread_state_panel.ts
+++ b/ui/src/frontend/thread_state_panel.ts
@@ -15,24 +15,22 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {translateState} from '../common/thread_state';
 import {timeToCode, toNs} from '../common/time';
 
 import {globals} from './globals';
 import {Panel, PanelSize} from './panel';
 import {scrollToTrackAndTs} from './scroll_helper';
 
-interface ThreadStateDetailsAttr {
-  utid: number;
-  ts: number;
-  dur: number;
-  state: string;
-  cpu: number;
-}
 
-export class ThreadStatePanel extends Panel<ThreadStateDetailsAttr> {
-  view({attrs}: m.CVnode<ThreadStateDetailsAttr>) {
-    const threadInfo = globals.threads.get(attrs.utid);
+export class ThreadStatePanel extends Panel {
+  view() {
+    const threadState = globals.threadStateDetails;
+    if (threadState === undefined || threadState.utid === undefined ||
+        threadState.ts === undefined || threadState.dur === undefined ||
+        threadState.state === undefined) {
+      return m('.details-panel');
+    }
+    const threadInfo = globals.threads.get(threadState.utid);
     if (threadInfo) {
       return m(
           '.details-panel',
@@ -40,43 +38,45 @@
           m('.details-table', [m('table.half-width', [
               m('tr',
                 m('th', `Start time`),
-                m('td',
-                  `${
-                      timeToCode(
-                          attrs.ts - globals.state.traceTime.startSec)}`)),
+                m('td', `${timeToCode(threadState.ts)}`)),
               m('tr',
                 m('th', `Duration`),
                 m(
                     'td',
-                    `${timeToCode(attrs.dur)} `,
+                    `${timeToCode(threadState.dur)} `,
                     )),
               m('tr',
                 m('th', `State`),
-                m('td', this.getStateContent(attrs.state, attrs.cpu))),
+                m('td',
+                  this.getStateContent(
+                      threadState.state,
+                      threadState.cpu,
+                      threadState.sliceId,
+                      threadState.ts))),
               m('tr',
                 m('th', `Process`),
                 m('td', `${threadInfo.procName} [${threadInfo.pid}]`)),
             ])]));
-    } else {
-      return m('.details-panel');
     }
+    return m('.details-panel');
   }
 
   renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {}
 
   // If it is the running state, we want to show which CPU and a button to
   // go to the sched slice. Otherwise, just show the state.
-  getStateContent(state: string, cpu: number) {
-    if (state !== 'Running') {
-      return [translateState(state)];
+  getStateContent(
+      state: string, cpu: number|undefined, sliceId: number|undefined,
+      ts: number) {
+    if (sliceId === undefined || cpu === undefined) {
+      return [state];
     }
 
     return [
-      `${translateState(state)} on CPU ${cpu}`,
+      `${state} on CPU ${cpu}`,
       m('i.material-icons.grey',
         {
           onclick: () => {
-            if (globals.sliceDetails.id && globals.sliceDetails.ts) {
               // TODO(taylori): Use trackId from TP.
               let trackId;
               for (const track of Object.values(globals.state.tracks)) {
@@ -86,15 +86,11 @@
                 }
               }
               if (trackId) {
-                globals.makeSelection(Actions.selectSlice(
-                    {id: globals.sliceDetails.id, trackId}));
+                globals.makeSelection(
+                    Actions.selectSlice({id: sliceId, trackId}));
                 scrollToTrackAndTs(
-                    trackId,
-                    toNs(
-                        globals.sliceDetails.ts +
-                        globals.state.traceTime.startSec));
+                    trackId, toNs(ts + globals.state.traceTime.startSec));
               }
-            }
           },
           title: 'Go to CPU slice'
         },
diff --git a/ui/src/frontend/time_scale.ts b/ui/src/frontend/time_scale.ts
index e163daa..4bc093b 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -12,6 +12,7 @@
 // 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';
 
 const MAX_ZOOM_SPAN_SEC = 1e-4;  // 0.1 ms.
@@ -60,6 +61,8 @@
   }
 
   setLimitsPx(pxStart: number, pxEnd: number) {
+    assertFalse(pxStart === pxEnd);
+    assertTrue(pxStart >= 0 && pxEnd >= 0);
     this._startPx = pxStart;
     this._endPx = pxEnd;
     this.updateSlope();
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index d5dc939..8ec0748 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -129,15 +129,35 @@
       ctx.fillRect(xAndTime[0], 0, 1, size.height);
     }
 
-    const selectedArea = globals.frontendLocalState.selectedArea.area;
-    if (selectedArea !== undefined) {
+    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));
+    } 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));
-    } else if (globals.frontendLocalState.hoveredLogsTimestamp !== -1) {
+    }
+
+    if (globals.frontendLocalState.hoveredLogsTimestamp !== -1) {
       this.renderHover(
           ctx, size, globals.frontendLocalState.hoveredLogsTimestamp);
     }
+
+    for (const note of Object.values(globals.state.notes)) {
+      const noteIsSelected = selection !== null && selection.kind === 'AREA' &&
+          selection.noteId === note.id;
+      if (note.noteType === 'AREA' && !noteIsSelected) {
+        const selectedArea = globals.state.areas[note.areaId];
+        this.renderSpan(
+            ctx,
+            size,
+            new TimeSpan(selectedArea.startSec, selectedArea.endSec));
+      }
+    }
   }
 
   renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: number) {
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index e8403c3..dfeb056 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -19,6 +19,7 @@
 
 import {globals} from './globals';
 import {executeSearch} from './search_handler';
+import {taskTracker} from './task_tracker';
 
 const SEARCH = Symbol('search');
 const COMMAND = Symbol('command');
@@ -110,17 +111,16 @@
         `.omnibox${commandMode ? '.command-mode' : ''}`,
         m('input', {
           placeholder: PLACEHOLDER[mode],
-          oninput: m.withAttr(
-              'value',
-              v => {
-                globals.frontendLocalState.setOmnibox(
-                    v, commandMode ? 'COMMAND' : 'SEARCH');
-                if (mode === SEARCH) {
-                  globals.frontendLocalState.setSearchIndex(-1);
-                  displayStepThrough = v.length >= 4;
-                  globals.rafScheduler.scheduleFullRedraw();
-                }
-              }),
+          oninput: (e: InputEvent) => {
+            const value = (e.target as HTMLInputElement).value;
+            globals.frontendLocalState.setOmnibox(
+                value, commandMode ? 'COMMAND' : 'SEARCH');
+            if (mode === SEARCH) {
+              globals.frontendLocalState.setSearchIndex(-1);
+              displayStepThrough = value.length >= 4;
+              globals.rafScheduler.scheduleFullRedraw();
+            }
+          },
           value: globals.frontendLocalState.omnibox,
         }),
         displayStepThrough ?
@@ -179,7 +179,7 @@
     if (this.progressBar === undefined) return;
     const engine: EngineConfig = globals.state.engines['0'];
     if ((engine !== undefined && !engine.ready) ||
-        globals.numQueuedQueries > 0) {
+        globals.numQueuedQueries > 0 || taskTracker.hasPendingTasks()) {
       this.progressBar.classList.add('progress-anim');
     } else {
       this.progressBar.classList.remove('progress-anim');
@@ -190,7 +190,12 @@
 
 class NewVersionNotification implements m.ClassComponent {
   view() {
-    if (!globals.frontendLocalState.newVersionAvailable) return;
+    const engine: EngineConfig = globals.state.engines['0'];
+    // Don't show the new version toast if a trace is loading (engine exists).
+    if (!globals.frontendLocalState.newVersionAvailable ||
+        engine !== undefined) {
+      return;
+    }
     return m(
         '.new-version-toast',
         'A new version of the UI is available!',
@@ -241,14 +246,15 @@
 class TraceErrorIcon implements m.ClassComponent {
   view() {
     const errors = globals.traceErrors;
-    if (!errors || mode === COMMAND) return;
+    if (!errors && !globals.metricError || mode === COMMAND) return;
+    const message = errors ? `${errors} import or data loss errors detected.` :
+                             `Metric error detected.`;
     return m(
         'a.error',
         {href: '#!/info'},
         m('i.material-icons',
           {
-            title: `${globals.traceErrors} import or data loss errors detected.
-                    Click for more info.`,
+            title: message + ` Click for more info.`,
           },
           'announcement'));
   }
@@ -258,6 +264,9 @@
   view() {
     return m(
         '.topbar',
+        {
+          class: globals.frontendLocalState.sidebarVisible ? '' : 'hide-sidebar'
+        },
         globals.frontendLocalState.newVersionAvailable ?
             m(NewVersionNotification) :
             m(Omnibox),
diff --git a/ui/src/frontend/trace_attrs.ts b/ui/src/frontend/trace_attrs.ts
new file mode 100644
index 0000000..2d6ffcf
--- /dev/null
+++ b/ui/src/frontend/trace_attrs.ts
@@ -0,0 +1,26 @@
+// 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.
+
+import {globals} from './globals';
+
+export function isShareable() {
+  return (globals.isInternalUser && isDownloadable());
+}
+
+export function isDownloadable() {
+  if (globals.frontendLocalState.localOnlyMode) return false;
+  const engine = Object.values(globals.state.engines)[0];
+  if (engine && engine.source.type === 'HTTP_RPC') return false;
+  return true;
+}
\ No newline at end of file
diff --git a/ui/src/frontend/trace_info_page.ts b/ui/src/frontend/trace_info_page.ts
index 0ecb13e..b1ba4d3 100644
--- a/ui/src/frontend/trace_info_page.ts
+++ b/ui/src/frontend/trace_info_page.ts
@@ -84,6 +84,17 @@
   }
 }
 
+class MetricErrors implements m.ClassComponent {
+  view() {
+    if (!globals.metricError) return;
+    return m(
+        `section.errors`,
+        m('h2', `Metric Errors`),
+        m('h3', `One or more metrics were not computed successfully:`),
+        m('div.metric-error', globals.metricError));
+  }
+}
+
 class TraceMetadata implements m.ClassComponent {
   private queryDispatched = false;
   private readonly QUERY_ID = 'info_metadata';
@@ -177,6 +188,7 @@
   view() {
     return m(
         '.trace-info-page',
+        m(MetricErrors),
         m(StatsSection, {
           queryId: 'info_errors',
           title: 'Import errors',
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 4df2730..2f491ae 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -35,19 +35,34 @@
   create(TrackState: TrackState): Track;
 }
 
+export interface SliceRect {
+  left: number;
+  width: number;
+  top: number;
+  height: number;
+  visible: boolean;
+}
+
 /**
  * The abstract class that needs to be implemented by all tracks.
  */
 export abstract class Track<Config = {}, Data extends TrackData = TrackData> {
-  constructor(protected trackState: TrackState) {}
+  private trackId: string;
+  constructor(trackState: TrackState) {
+    this.trackId = trackState.id;
+  }
   protected abstract renderCanvas(ctx: CanvasRenderingContext2D): void;
 
+  protected get trackState(): TrackState {
+    return globals.state.tracks[this.trackId];
+  }
+
   get config(): Config {
-    return this.trackState.config as Config;
+    return globals.state.tracks[this.trackId].config as Config;
   }
 
   data(): Data|undefined {
-    return globals.trackDataStore.get(this.trackState.id) as Data;
+    return globals.trackDataStore.get(this.trackId) as Data;
   }
 
   getHeight(): number {
@@ -116,4 +131,15 @@
       ctx.fillText(text2, xPos + 8, this.getHeight() / 2 + 6);
     }
   }
+
+  /**
+   * Returns a place where a given slice should be drawn. Should be implemented
+   * 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
+      |undefined {
+    return undefined;
+  }
 }
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 67d0742..c65af87 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -38,7 +38,6 @@
 import {trackRegistry} from './track_registry';
 import {
   drawVerticalLineAtTime,
-  drawVerticalSelection,
 } from './vertical_line_helper';
 
 interface Attrs {
@@ -64,8 +63,7 @@
   }
 
   get summaryTrackState(): TrackState {
-    return assertExists(
-        globals.state.tracks[this.trackGroupState.summaryTrackId]);
+    return assertExists(globals.state.tracks[this.trackGroupState.tracks[0]]);
   }
 
   view({attrs}: m.CVnode<Attrs>) {
@@ -88,10 +86,12 @@
       }
     }
 
-    const selectedArea = globals.frontendLocalState.selectedArea.area;
+    const selection = globals.state.currentSelection;
+
     const trackGroup = globals.state.trackGroups[attrs.trackGroupId];
     let checkBox = BLANK_CHECKBOX;
-    if (selectedArea) {
+    if (selection !== null && selection.kind === 'AREA') {
+      const selectedArea = globals.state.areas[selection.areaId];
       if (selectedArea.tracks.includes(attrs.trackGroupId) &&
           trackGroup.tracks.every(id => selectedArea.tracks.includes(id))) {
         checkBox = CHECKBOX;
@@ -124,16 +124,17 @@
               title: name,
             },
             name),
-          selectedArea ? m('i.material-icons.track-button',
-                           {
-                             onclick: (e: MouseEvent) => {
-                               globals.frontendLocalState.toggleTrackSelection(
-                                   attrs.trackGroupId, true /*trackGroup*/);
-                               e.stopPropagation();
-                             }
-                           },
-                           checkBox) :
-                         ''),
+          selection && selection.kind === 'AREA' ?
+              m('i.material-icons.track-button',
+                {
+                  onclick: (e: MouseEvent) => {
+                    globals.dispatch(Actions.toggleTrackSelection(
+                        {id: attrs.trackGroupId, isTrackGroup: true}));
+                    e.stopPropagation();
+                  }
+                },
+                checkBox) :
+              ''),
 
         this.summaryTrack ? m(TrackContent, {track: this.summaryTrack}) : null);
   }
@@ -145,32 +146,41 @@
   onupdate({dom}: m.CVnodeDOM<Attrs>) {
     const shell = assertExists(dom.querySelector('.shell'));
     this.shellWidth = shell.getBoundingClientRect().width;
-    this.backgroundColor =
-        getComputedStyle(dom).getPropertyValue('--collapsed-background');
+    // TODO(andrewbb): move this to css_constants
+    if (this.trackGroupState.collapsed) {
+      this.backgroundColor =
+          getComputedStyle(dom).getPropertyValue('--collapsed-background');
+    } else {
+      this.backgroundColor =
+          getComputedStyle(dom).getPropertyValue('--expanded-background');
+    }
   }
 
   highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
     const localState = globals.frontendLocalState;
-    const area = localState.selectedArea.area;
-    if (area && area.tracks.includes(this.trackGroupId)) {
-      ctx.fillStyle = '#ebeef9';
+    const selection = globals.state.currentSelection;
+    if (!selection || selection.kind !== 'AREA') return;
+    const selectedArea = globals.state.areas[selection.areaId];
+    if (selectedArea.tracks.includes(this.trackGroupId)) {
+      ctx.fillStyle = 'rgba(131, 152, 230, 0.3)';
       ctx.fillRect(
-          localState.timeScale.timeToPx(area.startSec) + this.shellWidth,
+          localState.timeScale.timeToPx(selectedArea.startSec) +
+              this.shellWidth,
           0,
-          localState.timeScale.deltaTimeToPx(area.endSec - area.startSec),
+          localState.timeScale.deltaTimeToPx(
+              selectedArea.endSec - selectedArea.startSec),
           size.height);
     }
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     const collapsed = this.trackGroupState.collapsed;
-    if (!collapsed) return;
-
-    ctx.save();
 
     ctx.fillStyle = this.backgroundColor;
     ctx.fillRect(0, 0, size.width, size.height);
 
+    if (!collapsed) return;
+
     this.highlightIfTrackSelected(ctx, size);
 
     drawGridLines(
@@ -180,13 +190,15 @@
         size.width,
         size.height);
 
+    ctx.save();
     ctx.translate(this.shellWidth, 0);
-
     if (this.summaryTrack) {
       this.summaryTrack.render(ctx);
     }
     ctx.restore();
 
+    this.highlightIfTrackSelected(ctx, size);
+
     const localState = globals.frontendLocalState;
     // Draw vertical line when hovering on the notes panel.
     if (localState.hoveredNoteTimestamp !== -1) {
@@ -203,31 +215,16 @@
           localState.timeScale,
           localState.hoveredLogsTimestamp,
           size.height,
-          `rgb(52,69,150)`);
-    }
-    if (localState.selectedArea.area !== undefined &&
-        !globals.frontendLocalState.selectingArea) {
-      drawVerticalSelection(
-          ctx,
-          localState.timeScale,
-          localState.selectedArea.area.startSec,
-          localState.selectedArea.area.endSec,
-          size.height,
-          `rgba(0,0,0,0.5)`);
+          `#344596`);
     }
     if (globals.state.currentSelection !== null) {
       if (globals.state.currentSelection.kind === 'NOTE') {
         const note = globals.state.notes[globals.state.currentSelection.id];
-        drawVerticalLineAtTime(ctx,
-                               localState.timeScale,
-                               note.timestamp,
-                               size.height,
-                               note.color);
-        if (note.noteType === 'AREA') {
+        if (note.noteType === 'DEFAULT') {
           drawVerticalLineAtTime(
               ctx,
               localState.timeScale,
-              note.area.endSec,
+              note.timestamp,
               size.height,
               note.color);
         }
@@ -251,14 +248,14 @@
         drawVerticalLineAtTime(
             ctx,
             localState.timeScale,
-            note.area.startSec,
+            globals.state.areas[note.areaId].startSec,
             size.height,
             transparentNoteColor,
             1);
         drawVerticalLineAtTime(
             ctx,
             localState.timeScale,
-            note.area.endSec,
+            globals.state.areas[note.areaId].endSec,
             size.height,
             transparentNoteColor,
             1);
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 92269ca..e982f99 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -25,11 +25,10 @@
 import {BLANK_CHECKBOX, CHECKBOX, STAR, STAR_BORDER} from './icons';
 import {Panel, PanelSize} from './panel';
 import {verticalScrollToTrack} from './scroll_helper';
-import {Track} from './track';
+import {SliceRect, Track} from './track';
 import {trackRegistry} from './track_registry';
 import {
   drawVerticalLineAtTime,
-  drawVerticalSelection
 } from './vertical_line_helper';
 
 function isPinned(id: string) {
@@ -37,8 +36,10 @@
 }
 
 function isSelected(id: string) {
-  return globals.frontendLocalState.selectedArea.area &&
-      globals.frontendLocalState.selectedArea.area.tracks.includes(id);
+  const selection = globals.state.currentSelection;
+  if (selection === null || selection.kind !== 'AREA') return false;
+  const selectedArea = globals.state.areas[selection.areaId];
+  return selectedArea.tracks.includes(id);
 }
 
 interface TrackShellAttrs {
@@ -98,17 +99,21 @@
             tooltip: isPinned(attrs.trackState.id) ? 'Unpin' : 'Pin to top',
             showButton: isPinned(attrs.trackState.id),
           }),
-          globals.frontendLocalState.selectedArea.area ? m(TrackButton, {
-            action: () => {
-              globals.frontendLocalState.toggleTrackSelection(
-                  attrs.trackState.id);
-            },
-            i: isSelected(attrs.trackState.id) ? CHECKBOX : BLANK_CHECKBOX,
-            tooltip: isSelected(attrs.trackState.id) ? 'Remove track' :
-                                                       'Add track to selection',
-            showButton: true,
-          }) :
-                                                         ''));
+          globals.state.currentSelection !== null &&
+                  globals.state.currentSelection.kind === 'AREA' ?
+              m(TrackButton, {
+                action: (e: PerfettoMouseEvent) => {
+                  globals.dispatch(Actions.toggleTrackSelection(
+                      {id: attrs.trackState.id, isTrackGroup: false}));
+                  e.stopPropagation();
+                },
+                i: isSelected(attrs.trackState.id) ? CHECKBOX : BLANK_CHECKBOX,
+                tooltip: isSelected(attrs.trackState.id) ?
+                    'Remove track' :
+                    'Add track to selection',
+                showButton: true,
+              }) :
+              ''));
   }
 
   onmousedown(e: MouseEvent) {
@@ -170,6 +175,10 @@
 
 export interface TrackContentAttrs { track: Track; }
 export class TrackContent implements m.ClassComponent<TrackContentAttrs> {
+  private mouseDownX?: number;
+  private mouseDownY?: number;
+  private selectionOccurred = false;
+
   view({attrs}: m.CVnode<TrackContentAttrs>) {
     return m('.track-content', {
       onmousemove: (e: PerfettoMouseEvent) => {
@@ -180,20 +189,32 @@
         attrs.track.onMouseOut();
         globals.rafScheduler.scheduleRedraw();
       },
+      onmousedown: (e: PerfettoMouseEvent) => {
+        this.mouseDownX = e.layerX;
+        this.mouseDownY = e.layerY;
+      },
+      onmouseup: (e: PerfettoMouseEvent) => {
+        if (this.mouseDownX === undefined || this.mouseDownY === undefined) {
+          return;
+        }
+        if (Math.abs(e.layerX - this.mouseDownX) > 1 ||
+            Math.abs(e.layerY - this.mouseDownY) > 1) {
+          this.selectionOccurred = true;
+        }
+        this.mouseDownX = undefined;
+        this.mouseDownY = undefined;
+      },
       onclick: (e: PerfettoMouseEvent) => {
-        // If we are selecting a time range - do not pass the click to the
-        // track.
-        if (globals.frontendLocalState.selectingArea) return;
-        // If the click is outside of the current time range, clear it.
-        const clickTime = globals.frontendLocalState.timeScale.pxToTime(
-            e.layerX - TRACK_SHELL_WIDTH);
-        const area = globals.frontendLocalState.selectedArea.area;
-        if (area !== undefined &&
-            (clickTime < area.startSec || clickTime > area.endSec)) {
-          globals.frontendLocalState.deselectArea();
-          e.stopPropagation();
-        } else if (attrs.track.onMouseClick(
-                       {x: e.layerX - TRACK_SHELL_WIDTH, y: e.layerY})) {
+        // This click event occurs after any selection mouse up/drag events
+        // so we have to look if the mouse moved during this click to know
+        // if a selection occurred.
+        if (this.selectionOccurred) {
+          this.selectionOccurred = false;
+          return;
+        }
+        // Returns true if something was selected, so stop propagation.
+        if (attrs.track.onMouseClick(
+                {x: e.layerX - TRACK_SHELL_WIDTH, y: e.layerY})) {
           e.stopPropagation();
         }
         globals.rafScheduler.scheduleRedraw();
@@ -231,7 +252,7 @@
 }
 
 export interface TrackButtonAttrs {
-  action: () => void;
+  action: (e: PerfettoMouseEvent) => void;
   i: string;
   tooltip: string;
   showButton: boolean;
@@ -270,14 +291,16 @@
 
   highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
     const localState = globals.frontendLocalState;
-    const area = localState.selectedArea.area;
-    if (area && area.tracks.includes(this.trackState.id)) {
+    const selection = globals.state.currentSelection;
+    if (!selection || selection.kind !== 'AREA') return;
+    const selectedArea = globals.state.areas[selection.areaId];
+    if (selectedArea.tracks.includes(this.trackState.id)) {
       const timeScale = localState.timeScale;
-      ctx.fillStyle = '#ebeef9';
+      ctx.fillStyle = 'rgba(131, 152, 230, 0.3)';
       ctx.fillRect(
-          timeScale.timeToPx(area.startSec) + TRACK_SHELL_WIDTH,
+          timeScale.timeToPx(selectedArea.startSec) + TRACK_SHELL_WIDTH,
           0,
-          timeScale.deltaTimeToPx(area.endSec - area.startSec),
+          timeScale.deltaTimeToPx(selectedArea.endSec - selectedArea.startSec),
           size.height);
     }
   }
@@ -285,8 +308,6 @@
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     ctx.save();
 
-    this.highlightIfTrackSelected(ctx, size);
-
     drawGridLines(
         ctx,
         globals.frontendLocalState.timeScale,
@@ -295,11 +316,11 @@
         size.height);
 
     ctx.translate(TRACK_SHELL_WIDTH, 0);
-
     this.track.render(ctx);
-
     ctx.restore();
 
+    this.highlightIfTrackSelected(ctx, size);
+
     const localState = globals.frontendLocalState;
     // Draw vertical line when hovering on the notes panel.
     if (localState.hoveredNoteTimestamp !== -1) {
@@ -316,31 +337,16 @@
           localState.timeScale,
           localState.hoveredLogsTimestamp,
           size.height,
-          `rgb(52,69,150)`);
-    }
-    if (localState.selectedArea.area !== undefined &&
-        !globals.frontendLocalState.selectingArea) {
-      drawVerticalSelection(
-          ctx,
-          localState.timeScale,
-          localState.selectedArea.area.startSec,
-          localState.selectedArea.area.endSec,
-          size.height,
-          `rgba(0,0,0,0.5)`);
+          `#344596`);
     }
     if (globals.state.currentSelection !== null) {
       if (globals.state.currentSelection.kind === 'NOTE') {
         const note = globals.state.notes[globals.state.currentSelection.id];
-        drawVerticalLineAtTime(ctx,
-                               localState.timeScale,
-                               note.timestamp,
-                               size.height,
-                               note.color);
-        if (note.noteType === 'AREA') {
+        if (note.noteType === 'DEFAULT') {
           drawVerticalLineAtTime(
               ctx,
               localState.timeScale,
-              note.area.endSec,
+              note.timestamp,
               size.height,
               note.color);
         }
@@ -365,18 +371,23 @@
         drawVerticalLineAtTime(
             ctx,
             localState.timeScale,
-            note.area.startSec,
+            globals.state.areas[note.areaId].startSec,
             size.height,
             transparentNoteColor,
             1);
         drawVerticalLineAtTime(
             ctx,
             localState.timeScale,
-            note.area.endSec,
+            globals.state.areas[note.areaId].endSec,
             size.height,
             transparentNoteColor,
             1);
       }
     }
   }
+
+  getSliceRect(tStart: number, tDur: number, depth: number): SliceRect
+      |undefined {
+    return this.track.getSliceRect(tStart, tDur, depth);
+  }
 }
diff --git a/ui/src/frontend/vertical_line_helper.ts b/ui/src/frontend/vertical_line_helper.ts
index d61fd70..b1e2cfc 100644
--- a/ui/src/frontend/vertical_line_helper.ts
+++ b/ui/src/frontend/vertical_line_helper.ts
@@ -41,24 +41,3 @@
     ctx.lineWidth = prevLineWidth;
 }
 
-// This draws two shaded rectangles outside of the area of interest. Effectivly
-// highlighting an area by colouring/darkening the outside areas.
-export function drawVerticalSelection(
-    ctx: CanvasRenderingContext2D,
-    timeScale: TimeScale,
-    timeStart: number,
-    timeEnd: number,
-    height: number,
-    color: string) {
-  const xStartPos =
-      TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(timeStart));
-  const xEndPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(timeEnd));
-  const width = timeScale.endPx;
-  ctx.fillStyle = color;
-  ctx.fillRect(0, 0, xStartPos, height);
-  // In the worst case xEndPos may be far to the left of the canvas (and so be
-  // <0) in this case fill the whole screen.
-  ctx.fillRect(Math.max(xEndPos, 0), 0, width + TRACK_SHELL_WIDTH, height);
-  drawVerticalLine(ctx, xStartPos, height, `rgba(52,69,150)`);
-  drawVerticalLine(ctx, xEndPos, height, `rgba(52,69,150)`);
-}
diff --git a/ui/src/frontend/video_panel.ts b/ui/src/frontend/video_panel.ts
index 9957e37..405258c 100644
--- a/ui/src/frontend/video_panel.ts
+++ b/ui/src/frontend/video_panel.ts
@@ -14,9 +14,10 @@
 
 import * as m from 'mithril';
 
-import {globals} from './globals';
 import {Actions} from '../common/actions';
-import {randomColor} from './colorizer';
+import {randomColor} from '../common/colorizer';
+
+import {globals} from './globals';
 
 export class VideoPanel implements m.ClassComponent {
   view() {
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 61c9df9..2d06c70 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -25,7 +25,6 @@
 import {createPage} from './pages';
 import {PanAndZoomHandler} from './pan_and_zoom_handler';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
-import {QueryTable} from './query_table';
 import {TickmarkPanel} from './tickmark_panel';
 import {TimeAxisPanel} from './time_axis_panel';
 import {computeZoom} from './time_scale';
@@ -40,8 +39,13 @@
 // Checks if the mousePos is within 3px of the start or end of the
 // current selected time range.
 function onTimeRangeBoundary(mousePos: number): 'START'|'END'|null {
-  const area = globals.frontendLocalState.selectedArea.area;
-  if (area !== undefined) {
+  const selection = globals.state.currentSelection;
+  if (selection !== null && selection.kind === 'AREA') {
+    // If frontend selectedArea exists then we are in the process of editing the
+    // time range and need to use that value instead.
+    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 startDrag = mousePos - TRACK_SHELL_WIDTH;
@@ -137,31 +141,36 @@
         const scale = frontendLocalState.timeScale;
         this.keepCurrentSelection = true;
         if (editing) {
-          const selectedArea = frontendLocalState.selectedArea.area;
-          if (selectedArea !== undefined) {
+          const selection = globals.state.currentSelection;
+          if (selection !== null && selection.kind === 'AREA') {
+            const area = globals.frontendLocalState.selectedArea ?
+                globals.frontendLocalState.selectedArea :
+                globals.state.areas[selection.areaId];
             let newTime = scale.pxToTime(currentX - TRACK_SHELL_WIDTH);
             // Have to check again for when one boundary crosses over the other.
             const curBoundary = onTimeRangeBoundary(prevX);
             if (curBoundary == null) return;
-            const keepTime = curBoundary === 'START' ? selectedArea.endSec :
-                                                       selectedArea.startSec;
+            const keepTime =
+                curBoundary === 'START' ? area.endSec : area.startSec;
             // Don't drag selection outside of current screen.
             if (newTime < keepTime) {
               newTime = Math.max(newTime, scale.pxToTime(scale.startPx));
             } else {
               newTime = Math.min(newTime, scale.pxToTime(scale.endPx));
             }
+            // 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),
-            );
+                globals.state.areas[selection.areaId].tracks);
           }
         } else {
-          const startPx = Math.max(
-              Math.min(dragStartX, currentX) - TRACK_SHELL_WIDTH,
-              scale.startPx);
-          const endPx = Math.min(
-              Math.max(dragStartX, currentX) - TRACK_SHELL_WIDTH, scale.endPx);
+          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);
           frontendLocalState.selectArea(
               scale.pxToTime(startPx), scale.pxToTime(endPx));
           frontendLocalState.areaY.start = dragStartY;
@@ -169,13 +178,25 @@
         }
         globals.rafScheduler.scheduleRedraw();
       },
-      selectingStarted: () => {
-        globals.frontendLocalState.selectingArea = true;
-      },
-      selectingEnded: () => {
-        globals.frontendLocalState.selectingArea = false;
+      endSelection: (edit: boolean) => {
         globals.frontendLocalState.areaY.start = undefined;
         globals.frontendLocalState.areaY.end = undefined;
+        const area = globals.frontendLocalState.selectedArea;
+        // If we are editing we need to pass the current id through to ensure
+        // the marked area with that id is also updated.
+        if (edit) {
+          const selection = globals.state.currentSelection;
+          if (selection !== null && selection.kind === 'AREA' && area) {
+            globals.dispatch(
+                Actions.editArea({area, areaId: selection.areaId}));
+          }
+        } else if (area) {
+          globals.makeSelection(Actions.selectArea({area}));
+        }
+        // Now the selection has ended we stored the final selected area in the
+        // global state and can remove the in progress selection from the
+        // frontendLocalState.
+        globals.frontendLocalState.deselectArea();
         // Full redraw to color track shell.
         globals.rafScheduler.scheduleFullRedraw();
       }
@@ -198,15 +219,17 @@
         selectable: true,
       }));
       if (group.collapsed) continue;
-      for (const trackId of group.tracks) {
+      // The first track is the summary track, and is displayed as part of the
+      // group panel, we don't want to display it twice so we start from 1.
+      for (let i = 1; i < group.tracks.length; ++i) {
+        const id = group.tracks[i];
         scrollingPanels.push(m(TrackPanel, {
-          key: `track-${group.id}-${trackId}`,
-          id: trackId,
+          key: `track-${group.id}-${id}`,
+          id,
           selectable: true,
         }));
       }
     }
-    scrollingPanels.unshift(m(QueryTable, {key: 'query', queryId: 'command'}));
 
     return m(
         '.page',
diff --git a/ui/src/tracks/all_controller.ts b/ui/src/tracks/all_controller.ts
index 3d86dc5..116c1b9 100644
--- a/ui/src/tracks/all_controller.ts
+++ b/ui/src/tracks/all_controller.ts
@@ -25,3 +25,4 @@
 import './process_summary/controller';
 import './thread_state/controller';
 import './async_slices/controller';
+import './debug_slices/controller';
diff --git a/ui/src/tracks/all_frontend.ts b/ui/src/tracks/all_frontend.ts
index bfe9f26..6311137 100644
--- a/ui/src/tracks/all_frontend.ts
+++ b/ui/src/tracks/all_frontend.ts
@@ -25,3 +25,4 @@
 import './process_summary/frontend';
 import './thread_state/frontend';
 import './async_slices/frontend';
+import './debug_slices/frontend';
diff --git a/ui/src/tracks/android_log/controller.ts b/ui/src/tracks/android_log/controller.ts
index fba13d7..afd235c 100644
--- a/ui/src/tracks/android_log/controller.ts
+++ b/ui/src/tracks/android_log/controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNsCeil, toNsFloor} from '../../common/time';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -42,7 +43,7 @@
       group by ts_quant, prio
       order by ts_quant, prio limit ${LIMIT};`);
 
-    const rowCount = +rawResult.numRecords;
+    const rowCount = slowlyCountRows(rawResult);
     const result = {
       start,
       end,
diff --git a/ui/src/tracks/async_slices/common.ts b/ui/src/tracks/async_slices/common.ts
index 7bdd794..05ab816 100644
--- a/ui/src/tracks/async_slices/common.ts
+++ b/ui/src/tracks/async_slices/common.ts
@@ -28,4 +28,6 @@
   ends: Float64Array;
   depths: Uint16Array;
   titles: Uint16Array;  // Index in |strings|.
-}
\ No newline at end of file
+  isInstant: Uint16Array;
+  isIncomplete: Uint16Array;
+}
diff --git a/ui/src/tracks/async_slices/controller.ts b/ui/src/tracks/async_slices/controller.ts
index 5058378..96bd6e4 100644
--- a/ui/src/tracks/async_slices/controller.ts
+++ b/ui/src/tracks/async_slices/controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
 import {
   TrackController,
@@ -33,7 +34,7 @@
 
     // 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.round(resolution * 1e9 * pxSize / 2) * 2;
+    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
 
     if (this.maxDurNs === 0) {
       const maxDurResult = await this.query(`
@@ -41,7 +42,7 @@
         from experimental_slice_layout
         where filter_track_ids = '${this.config.trackIds.join(',')}'
       `);
-      if (maxDurResult.numRecords === 1) {
+      if (slowlyCountRows(maxDurResult) === 1) {
         this.maxDurNs = maxDurResult.columns[0].longValues![0];
       }
     }
@@ -53,16 +54,19 @@
         max(dur) as dur,
         layout_depth,
         name,
-        id
+        id,
+        dur = 0 as is_instant,
+        dur = -1 as is_incomplete
       from experimental_slice_layout
       where
         filter_track_ids = '${this.config.trackIds.join(',')}' and
         ts >= ${startNs - this.maxDurNs} and
         ts <= ${endNs}
-      group by tsq
+      group by tsq, layout_depth
+      order by tsq, layout_depth
     `);
 
-    const numRows = +rawResult.numRecords;
+    const numRows = slowlyCountRows(rawResult);
     const slices: Data = {
       start,
       end,
@@ -74,6 +78,8 @@
       ends: new Float64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
+      isInstant: new Uint16Array(numRows),
+      isIncomplete: new Uint16Array(numRows),
     };
 
     const stringIndexes = new Map<string, number>();
@@ -105,6 +111,8 @@
       slices.depths[row] = +cols[3].longValues![row];
       slices.titles[row] = internString(cols[4].stringValues![row]);
       slices.sliceIds[row] = +cols[5].longValues![row];
+      slices.isInstant[row] = +cols[6].longValues![row];
+      slices.isIncomplete[row] = +cols[7].longValues![row];
     }
     return slices;
   }
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index 59f6377..5ed3cb7 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -30,4 +30,6 @@
   ends: Float64Array;
   depths: Uint16Array;
   titles: Uint16Array;  // Index into strings.
-}
\ No newline at end of file
+  isInstant: Uint16Array;
+  isIncomplete: Uint16Array;
+}
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 61fab49..b1f2101 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
 import {
   TrackController,
@@ -34,14 +35,14 @@
 
     // 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.round(resolution * 1e9 * pxSize / 2) * 2;
+    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
     const tableName = this.namespaceTable('slice');
 
     if (this.maxDurNs === 0) {
       const query = `SELECT max(dur) FROM ${tableName} WHERE track_id = ${
           this.config.trackId}`;
       const rawResult = await this.query(query);
-      if (rawResult.numRecords === 1) {
+      if (slowlyCountRows(rawResult) === 1) {
         this.maxDurNs = rawResult.columns[0].longValues![0];
       }
     }
@@ -53,7 +54,9 @@
         max(dur),
         depth,
         id as slice_id,
-        name
+        name,
+        dur = 0 as is_instant,
+        dur = -1 as is_incomplete
       FROM ${tableName}
       WHERE track_id = ${this.config.trackId} AND
         ts >= (${startNs - this.maxDurNs}) AND
@@ -61,18 +64,20 @@
       GROUP BY depth, tsq`;
     const rawResult = await this.query(query);
 
-    const numRows = +rawResult.numRecords;
+    const numRows = slowlyCountRows(rawResult);
     const slices: Data = {
       start,
       end,
       resolution,
-      length: +rawResult.numRecords,
+      length: numRows,
       strings: [],
       sliceIds: new Float64Array(numRows),
       starts: new Float64Array(numRows),
       ends: new Float64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
+      isInstant: new Uint16Array(numRows),
+      isIncomplete: new Uint16Array(numRows),
     };
 
     const stringIndexes = new Map<string, number>();
@@ -91,12 +96,18 @@
       const startNs = +cols[1].longValues![row];
       const durNs = +cols[2].longValues![row];
       const endNs = startNs + durNs;
+      const isInstant = +cols[6].longValues![row];
+      const isIncomplete = +cols[7].longValues![row];
 
       let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
       endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
 
-      if (startNsQ === endNsQ) {
-        throw new Error('Should never happen');
+      if (!isInstant && startNsQ === endNsQ) {
+        throw new Error(
+            'Expected startNsQ and endNsQ to differ (' +
+            `startNsQ: ${startNsQ}, startNs: ${startNs},` +
+            ` endNsQ: ${endNsQ}, durNs: ${durNs},` +
+            ` endNs: ${endNs}, bucketNs: ${bucketNs})`);
       }
 
       slices.starts[row] = fromNs(startNsQ);
@@ -104,6 +115,8 @@
       slices.depths[row] = +cols[3].longValues![row];
       slices.sliceIds[row] = +cols[4].longValues![row];
       slices.titles[row] = internString(cols[5].stringValues![row]);
+      slices.isInstant[row] = isInstant;
+      slices.isIncomplete[row] = isIncomplete;
     }
     return slices;
   }
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 8990b8b..83795ce 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -14,11 +14,11 @@
 
 import {Actions} from '../../common/actions';
 import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
+import {hueForSlice} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
-import {toNs} from '../../common/time';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
-import {Track} from '../../frontend/track';
+import {SliceRect, Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
 
 import {Config, Data, SLICE_TRACK_KIND} from './common';
@@ -26,15 +26,11 @@
 const SLICE_HEIGHT = 18;
 const TRACK_PADDING = 4;
 const INCOMPLETE_SLICE_TIME_S = 0.00003;
-
-function hash(s: string): number {
-  let hash = 0x811c9dc5 & 0xfffffff;
-  for (let i = 0; i < s.length; i++) {
-    hash ^= s.charCodeAt(i);
-    hash = (hash * 16777619) & 0xffffffff;
-  }
-  return hash & 0xff;
-}
+const CHEVRON_WIDTH_PX = 10;
+const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2;
+const INNER_CHEVRON_OFFSET = -3;
+const INNER_CHEVRON_SCALE =
+    (SLICE_HEIGHT - 2 * INNER_CHEVRON_OFFSET) / SLICE_HEIGHT;
 
 export class ChromeSliceTrack extends Track<Config, Data> {
   static readonly kind: string = SLICE_TRACK_KIND;
@@ -72,7 +68,6 @@
 
     // measuretext is expensive so we only use it once.
     const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
-    const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
 
     // The draw of the rect on the selected slice must happen after the other
     // drawings, otherwise it would result under another rect.
@@ -84,95 +79,149 @@
       const depth = data.depths[i];
       const titleId = data.titles[i];
       const sliceId = data.sliceIds[i];
+      const isInstant = data.isInstant[i];
+      const isIncomplete = data.isIncomplete[i];
       const title = data.strings[titleId];
-      let incompleteSlice = false;
-
-      if (toNs(tEnd) - toNs(tStart) === -1) {  // incomplete slice
-        incompleteSlice = true;
+      if (isIncomplete) {  // incomplete slice
         tEnd = tStart + INCOMPLETE_SLICE_TIME_S;
       }
 
-      if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
+      const rect = this.getSliceRect(tStart, tEnd, depth);
+      if (!rect || !rect.visible) {
         continue;
       }
 
-      const rectXStart = Math.max(timeScale.timeToPx(tStart), 0);
-      let rectXEnd = Math.min(timeScale.timeToPx(tEnd), pxEnd);
-      let rectWidth = rectXEnd - rectXStart;
-      // All slices should be at least 1px.
-      if (rectWidth < 1) {
-        rectWidth = 1;
-        rectXEnd = rectXStart + 1;
-      }
-      const rectYStart = TRACK_PADDING + depth * SLICE_HEIGHT;
-      const name = title.replace(/( )?\d+/g, '');
-      const hue = hash(name);
-      const saturation = 50;
-      const hovered = titleId === this.hoveredTitleId;
-      const color = `hsl(${hue}, ${saturation}%, ${hovered ? 30 : 65}%)`;
-      ctx.fillStyle = color;
-      if (incompleteSlice && rectWidth > SLICE_HEIGHT / 4) {
-        drawIncompleteSlice(
-            ctx, rectXStart, rectYStart, rectWidth, SLICE_HEIGHT, color);
-      } else {
-        ctx.fillRect(rectXStart, rectYStart, rectWidth, SLICE_HEIGHT);
-      }
-
-      // Selected case
       const currentSelection = globals.state.currentSelection;
-      if (currentSelection && currentSelection.kind === 'CHROME_SLICE' &&
-          currentSelection.id !== undefined &&
-          currentSelection.id === sliceId) {
+      const isSelected = currentSelection &&
+          currentSelection.kind === 'CHROME_SLICE' &&
+          currentSelection.id !== undefined && currentSelection.id === sliceId;
+
+      const name = title.replace(/( )?\d+/g, '');
+      const hue = hueForSlice(name);
+      const saturation = isSelected ? 80 : 50;
+      const highlighted = titleId === this.hoveredTitleId ||
+          globals.frontendLocalState.highlightedSliceId === sliceId;
+      const color = `hsl(${hue}, ${saturation}%, ${highlighted ? 30 : 65}%)`;
+
+      ctx.fillStyle = color;
+
+      // We draw instant events as upward facing chevrons starting at A:
+      //     A
+      //    ###
+      //   ##C##
+      //  ##   ##
+      // D       B
+      // Then B, C, D and back to A:
+      if (isInstant) {
+        if (isSelected) {
+          drawRectOnSelected = () => {
+            ctx.save();
+            ctx.translate(rect.left, rect.top);
+
+            // Draw outer chevron as dark border
+            ctx.save();
+            ctx.translate(0, INNER_CHEVRON_OFFSET);
+            ctx.scale(INNER_CHEVRON_SCALE, INNER_CHEVRON_SCALE);
+            ctx.fillStyle = `hsl(${hue}, ${saturation}%, 30%)`;
+            this.drawChevron(ctx);
+            ctx.restore();
+
+            // Draw inner chevron as interior
+            ctx.fillStyle = color;
+            this.drawChevron(ctx);
+
+            ctx.restore();
+          };
+        } else {
+          ctx.save();
+          ctx.translate(rect.left, rect.top);
+          this.drawChevron(ctx);
+          ctx.restore();
+        }
+        continue;
+      }
+      if (isIncomplete && rect.width > SLICE_HEIGHT / 4) {
+        drawIncompleteSlice(
+            ctx, rect.left, rect.top, rect.width, SLICE_HEIGHT, color);
+      } else {
+        ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT);
+      }
+      // Selected case
+      if (isSelected) {
         drawRectOnSelected = () => {
           ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 30%)`;
           ctx.beginPath();
           ctx.lineWidth = 3;
           ctx.strokeRect(
-              rectXStart, rectYStart - 1.5, rectWidth, SLICE_HEIGHT + 3);
+              rect.left, rect.top - 1.5, rect.width, SLICE_HEIGHT + 3);
           ctx.closePath();
         };
       }
 
       ctx.fillStyle = 'white';
-      const displayText = cropText(title, charWidth, rectWidth);
-      const rectXCenter = rectXStart + rectWidth / 2;
+      const displayText = cropText(title, charWidth, rect.width);
+      const rectXCenter = rect.left + rect.width / 2;
       ctx.textBaseline = "middle";
-      ctx.fillText(displayText, rectXCenter, rectYStart + SLICE_HEIGHT / 2);
+      ctx.fillText(displayText, rectXCenter, rect.top + SLICE_HEIGHT / 2);
     }
     drawRectOnSelected();
   }
 
+  drawChevron(ctx: CanvasRenderingContext2D) {
+    // Draw a chevron at a fixed location and size. Should be used with
+    // ctx.translate and ctx.scale to alter location and size.
+    ctx.beginPath();
+    ctx.moveTo(0, 0);
+    ctx.lineTo(HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
+    ctx.lineTo(0, SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX);
+    ctx.lineTo(-HALF_CHEVRON_WIDTH_PX, SLICE_HEIGHT);
+    ctx.lineTo(0, 0);
+    ctx.fill();
+  }
+
   getSliceIndex({x, y}: {x: number, y: number}): number|void {
     const data = this.data();
-    this.hoveredTitleId = -1;
     if (data === undefined) return;
     const {timeScale} = globals.frontendLocalState;
     if (y < TRACK_PADDING) return;
+    const instantWidthTime = timeScale.deltaPxToDuration(HALF_CHEVRON_WIDTH_PX);
     const t = timeScale.pxToTime(x);
-    const depth = Math.floor(y / SLICE_HEIGHT);
+    const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT);
     for (let i = 0; i < data.starts.length; i++) {
-      const tStart = data.starts[i];
-      let tEnd = data.ends[i];
-      if (toNs(tEnd) - toNs(tStart) === -1) {
-        tEnd = tStart + INCOMPLETE_SLICE_TIME_S;
+      if (depth !== data.depths[i]) {
+        continue;
       }
-      if (tStart <= t && t <= tEnd && depth === data.depths[i]) {
-        return i;
+      const tStart = data.starts[i];
+      if (data.isInstant[i]) {
+        if (Math.abs(tStart - t) < instantWidthTime) {
+          return i;
+        }
+      } else {
+        let tEnd = data.ends[i];
+        if (data.isIncomplete[i]) {
+          tEnd = tStart + INCOMPLETE_SLICE_TIME_S;
+        }
+        if (tStart <= t && t <= tEnd) {
+          return i;
+        }
       }
     }
   }
 
   onMouseMove({x, y}: {x: number, y: number}) {
+    this.hoveredTitleId = -1;
+    globals.frontendLocalState.setHighlightedSliceId(-1);
     const sliceIndex = this.getSliceIndex({x, y});
     if (sliceIndex === undefined) return;
     const data = this.data();
     if (data === undefined) return;
-    const titleId = data.titles[sliceIndex];
-    this.hoveredTitleId = titleId;
+    this.hoveredTitleId = data.titles[sliceIndex];
+    globals.frontendLocalState.setHighlightedSliceId(data.sliceIds[sliceIndex]);
   }
 
   onMouseOut() {
     this.hoveredTitleId = -1;
+    globals.frontendLocalState.setHighlightedSliceId(-1);
   }
 
   onMouseClick({x, y}: {x: number, y: number}): boolean {
@@ -195,6 +244,22 @@
   getHeight() {
     return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
   }
+
+  getSliceRect(tStart: number, tEnd: number, depth: number): SliceRect
+      |undefined {
+    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
+    const left = Math.max(timeScale.timeToPx(tStart), 0);
+    const right = Math.min(timeScale.timeToPx(tEnd), pxEnd);
+    return {
+      left,
+      width: Math.max(right - left, 1),
+      top: TRACK_PADDING + depth * SLICE_HEIGHT,
+      height: SLICE_HEIGHT,
+      visible:
+          !(tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end)
+    };
+  }
 }
 
 trackRegistry.register(ChromeSliceTrack);
diff --git a/ui/src/tracks/counter/controller.ts b/ui/src/tracks/counter/controller.ts
index c57ee20..fa678cc 100644
--- a/ui/src/tracks/counter/controller.ts
+++ b/ui/src/tracks/counter/controller.ts
@@ -41,7 +41,7 @@
 
     // 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.round(resolution * 1e9 * pxSize / 2) * 2;
+    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
 
     if (!this.setup) {
       if (this.config.namespace === undefined) {
@@ -75,7 +75,7 @@
             )
           from ${this.tableName('counter_view')}
       `);
-      if (maxDurResult.numRecords === 1) {
+      if (slowlyCountRows(maxDurResult) === 1) {
         this.maxDurNs = maxDurResult.columns[0].longValues![0];
       }
 
diff --git a/ui/src/tracks/cpu_freq/controller.ts b/ui/src/tracks/cpu_freq/controller.ts
index 115b20d..08ca0b5 100644
--- a/ui/src/tracks/cpu_freq/controller.ts
+++ b/ui/src/tracks/cpu_freq/controller.ts
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertTrue} from '../../base/logging';
+import {RawQueryResult} from '../../common/protos';
 import {iter, NUM, slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
-
 import {
   TrackController,
   trackControllerRegistry
@@ -28,99 +29,61 @@
 
 class CpuFreqTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_FREQ_TRACK_KIND;
-  private setup = false;
+
   private maxDurNs = 0;
   private maxTsEndNs = 0;
   private maximumValueSeen = 0;
+  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
+
+  async onSetup() {
+    await this.createFreqIdleViews();
+
+    this.maximumValueSeen = await this.queryMaxFrequency();
+    this.maxDurNs = await this.queryMaxSourceDur();
+
+    const result = await this.query(`
+      select max(ts), dur, count(1)
+      from ${this.tableName('freq_idle')}
+    `);
+    this.maxTsEndNs =
+        result.columns[0].longValues![0] + result.columns[1].longValues![0];
+
+    const rowCount = result.columns[2].longValues![0];
+    const bucketNs = this.cachedBucketSizeNs(rowCount);
+    if (bucketNs === undefined) {
+      return;
+    }
+    await this.query(`
+      create table ${this.tableName('freq_idle_cached')} as
+      select
+        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
+        min(freq_value) as min_freq,
+        max(freq_value) as max_freq,
+        value_at_max_ts(ts, freq_value) as last_freq,
+        value_at_max_ts(ts, idle_value) as last_idle_value
+      from ${this.tableName('freq_idle')}
+      group by cached_tsq
+      order by cached_tsq
+    `);
+    this.cachedBucketNs = bucketNs;
+  }
 
   async onBoundsChange(start: number, end: number, resolution: number):
       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);
+
     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.round(resolution * 1e9 * pxSize / 2) * 2;
+    const bucketNs =
+        Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
 
-    if (!this.setup) {
-      const result = await this.query(`
-        select max(value)
-        from counter
-        where track_id = ${this.config.freqTrackId}`);
-      this.maximumValueSeen = +result.columns[0].doubleValues![0];
-
-      await this.query(`create view ${this.tableName('freq')} as
-        select
-          ts,
-          dur,
-          value as freq_value
-        from experimental_counter_dur c
-        where track_id = ${this.config.freqTrackId};
-      `);
-
-      const maxDurFreqResult =
-          await this.query(`select max(dur) from ${this.tableName('freq')}`);
-      if (maxDurFreqResult.numRecords === 1) {
-        this.maxDurNs = maxDurFreqResult.columns[0].longValues![0];
-      }
-
-      if (this.config.idleTrackId === undefined) {
-        await this.query(`create view ${this.tableName('freq_idle')} as
-          select
-            ts,
-            dur,
-            -1 as idle_value,
-            freq_value
-          from ${this.tableName('freq')};
-        `);
-      } else {
-        await this.query(`create view ${this.tableName('idle')} as
-          select
-            ts,
-            dur,
-            iif(value = 4294967295, -1, cast(value as int)) as idle_value
-          from experimental_counter_dur c
-          where track_id = ${this.config.idleTrackId};
-        `);
-
-        const maxDurIdleResult =
-            await this.query(`select max(dur) from ${this.tableName('idle')}`);
-        if (maxDurIdleResult.numRecords === 1) {
-          this.maxDurNs = Math.max(
-              this.maxDurNs, maxDurIdleResult.columns[0].longValues![0]);
-        }
-
-        await this.query(`create virtual table ${this.tableName('freq_idle')}
-          using span_join(${this.tableName('freq')},
-                          ${this.tableName('idle')});`);
-      }
-
-      const maxTsResult = await this.query(
-          `select max(ts), dur from ${this.tableName('freq_idle')}`);
-      if (maxTsResult.numRecords === 1) {
-        this.maxTsEndNs = maxTsResult.columns[0].longValues![0] +
-            maxTsResult.columns[1].longValues![0];
-      }
-
-      this.setup = true;
-    }
-
-    const geqConstraint = this.config.idleTrackId === undefined ?
-        `ts >= ${startNs - this.maxDurNs}` :
-        `source_geq(ts, ${startNs} - ${this.maxDurNs})`;
-    const freqResult = await this.query(`
-      select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
-        min(freq_value) as minFreq,
-        max(freq_value) as maxFreq,
-        value_at_max_ts(ts, freq_value) as lastFreq,
-        value_at_max_ts(ts, idle_value) as lastIdleValue
-      from ${this.tableName('freq_idle')}
-      where ${geqConstraint} and ts <= ${endNs}
-      group by tsq
-    `);
+    const freqResult = await this.queryData(startNs, endNs, bucketNs);
 
     const numRows = slowlyCountRows(freqResult);
     const data: Data = {
@@ -156,10 +119,120 @@
     return data;
   }
 
+  private async queryData(startNs: number, endNs: number, bucketNs: number):
+      Promise<RawQueryResult> {
+    const isCached = this.cachedBucketNs <= bucketNs;
+
+    if (isCached) {
+      return this.query(`
+        select
+          cached_tsq / ${bucketNs} * ${bucketNs} as tsq,
+          min(min_freq) as minFreq,
+          max(max_freq) as maxFreq,
+          value_at_max_ts(cached_tsq, last_freq) as lastFreq,
+          value_at_max_ts(cached_tsq, last_idle_value) as lastIdleValue
+        from ${this.tableName('freq_idle_cached')}
+        where
+          cached_tsq >= ${startNs - this.maxDurNs} and
+          cached_tsq <= ${endNs}
+        group by tsq
+        order by tsq
+      `);
+    }
+
+    const minTsFreq = await this.query(`
+      select ifnull(max(ts), 0) from ${this.tableName('freq')}
+      where ts < ${startNs}
+    `);
+    let minTs = minTsFreq.columns[0].longValues![0];
+    if (this.config.idleTrackId !== undefined) {
+      const minTsIdle = await this.query(`
+        select ifnull(max(ts), 0) from ${this.tableName('idle')}
+        where ts < ${startNs}
+      `);
+      minTs = Math.min(minTsIdle.columns[0].longValues![0], minTs);
+    }
+    const geqConstraint = this.config.idleTrackId === undefined ?
+        `ts >= ${minTs}` :
+        `source_geq(ts, ${minTs})`;
+    return this.query(`
+      select
+        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        min(freq_value) as minFreq,
+        max(freq_value) as maxFreq,
+        value_at_max_ts(ts, freq_value) as lastFreq,
+        value_at_max_ts(ts, idle_value) as lastIdleValue
+      from ${this.tableName('freq_idle')}
+      where
+        ${geqConstraint} and
+        ts <= ${endNs}
+      group by tsq
+      order by tsq
+    `);
+  }
+
+  private async queryMaxFrequency(): Promise<number> {
+    const result = await this.query(`
+      select max(freq_value)
+      from ${this.tableName('freq')}
+    `);
+    return result.columns[0].doubleValues![0];
+  }
+
+  private async queryMaxSourceDur(): Promise<number> {
+    const maxDurFreqResult =
+        await this.query(`select max(dur) from ${this.tableName('freq')}`);
+    const maxFreqDurNs = maxDurFreqResult.columns[0].longValues![0];
+    if (this.config.idleTrackId === undefined) {
+      return maxFreqDurNs;
+    }
+
+    const maxDurIdleResult =
+        await this.query(`select max(dur) from ${this.tableName('idle')}`);
+    return Math.max(maxFreqDurNs, maxDurIdleResult.columns[0].longValues![0]);
+  }
+
+  private async createFreqIdleViews() {
+    await this.query(`create view ${this.tableName('freq')} as
+      select
+        ts,
+        dur,
+        value as freq_value
+      from experimental_counter_dur c
+      where track_id = ${this.config.freqTrackId};
+    `);
+
+    if (this.config.idleTrackId === undefined) {
+      await this.query(`create view ${this.tableName('freq_idle')} as
+        select
+          ts,
+          dur,
+          -1 as idle_value,
+          freq_value
+        from ${this.tableName('freq')};
+      `);
+      return;
+    }
+
+    await this.query(`
+      create view ${this.tableName('idle')} as
+      select
+        ts,
+        dur,
+        iif(value = 4294967295, -1, cast(value as int)) as idle_value
+      from experimental_counter_dur c
+      where track_id = ${this.config.idleTrackId};
+    `);
+
+    await this.query(`
+      create virtual table ${this.tableName('freq_idle')}
+      using span_join(${this.tableName('freq')}, ${this.tableName('idle')});
+    `);
+  }
+
   private maximumValue() {
     return Math.max(this.config.maximumValue || 0, this.maximumValueSeen);
   }
-
 }
 
 
diff --git a/ui/src/tracks/cpu_freq/frontend.ts b/ui/src/tracks/cpu_freq/frontend.ts
index e642e3f..4e5c9e4 100644
--- a/ui/src/tracks/cpu_freq/frontend.ts
+++ b/ui/src/tracks/cpu_freq/frontend.ts
@@ -14,9 +14,9 @@
 
 import {searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
+import {hueForCpu} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
 import {checkerboardExcept} from '../../frontend/checkerboard';
-import {hueForCpu} from '../../frontend/colorizer';
 import {globals} from '../../frontend/globals';
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
@@ -66,7 +66,7 @@
     assertTrue(data.timestamps.length === data.maxFreqKHz.length);
     assertTrue(data.timestamps.length === data.lastIdleValues.length);
 
-    const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end));
+    const endPx = timeScale.timeToPx(visibleWindowTime.end);
     const zeroY = MARGIN_TOP + RECT_HEIGHT;
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -96,11 +96,18 @@
       return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
     };
 
+    const [rawStartIdx,] =
+      searchSegment(data.timestamps, visibleWindowTime.start);
+    const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
+
+    const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end);
+    const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
+
     ctx.beginPath();
-    ctx.moveTo(calculateX(data.timestamps[0]), zeroY);
+    ctx.moveTo(Math.max(calculateX(data.timestamps[startIdx]), 0), zeroY);
 
     let lastDrawnY = zeroY;
-    for (let i = 0; i < data.timestamps.length; i++) {
+    for (let i = startIdx; i < endIdx; i++) {
       const x = calculateX(data.timestamps[i]);
 
       const minY = calculateY(data.minFreqKHz[i]);
@@ -120,7 +127,7 @@
     }
     // Find the end time for the last frequency event and then draw
     // down to zero to show that we do not have data after that point.
-    const finalX = calculateX(data.maxTsEnd);
+    const finalX = Math.min(calculateX(data.maxTsEnd), endPx);
     ctx.lineTo(finalX, lastDrawnY);
     ctx.lineTo(finalX, zeroY);
     ctx.lineTo(endPx, zeroY);
diff --git a/ui/src/tracks/cpu_profile/controller.ts b/ui/src/tracks/cpu_profile/controller.ts
index cb850ee..fa9a6b3 100644
--- a/ui/src/tracks/cpu_profile/controller.ts
+++ b/ui/src/tracks/cpu_profile/controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {
   TrackController,
   trackControllerRegistry
@@ -33,7 +34,7 @@
 
     const result = await this.query(query);
 
-    const numRows = +result.numRecords;
+    const numRows = slowlyCountRows(result);
     const data: Data = {
       start,
       end,
diff --git a/ui/src/tracks/cpu_slices/common.ts b/ui/src/tracks/cpu_slices/common.ts
index 8bc0a09..c87bbe9 100644
--- a/ui/src/tracks/cpu_slices/common.ts
+++ b/ui/src/tracks/cpu_slices/common.ts
@@ -16,16 +16,7 @@
 
 export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
 
-export interface SummaryData extends TrackData {
-  kind: 'summary';
-
-  bucketSizeSeconds: number;
-  utilizations: Float64Array;
-}
-
-export interface SliceData extends TrackData {
-  kind: 'slice';
-
+export interface Data extends TrackData {
   // Slices are stored in a columnar fashion. All fields have the same length.
   ids: Float64Array;
   starts: Float64Array;
@@ -33,7 +24,4 @@
   utids: Uint32Array;
 }
 
-export type Data = SummaryData | SliceData;
-
-
 export interface Config { cpu: number; }
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index 35345ba..1389cb4 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -12,56 +12,101 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertTrue} from '../../base/logging';
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
-
 import {
   TrackController,
   trackControllerRegistry
 } from '../../controller/track_controller';
 
-import {Config, CPU_SLICE_TRACK_KIND, Data, SliceData} from './common';
+import {Config, CPU_SLICE_TRACK_KIND, Data} from './common';
 
 class CpuSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
+
+  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
   private maxDurNs = 0;
 
+  async onSetup() {
+    await this.query(`
+      create view ${this.tableName('sched')} as
+      select
+        ts,
+        dur,
+        utid,
+        id
+      from sched
+      where cpu = ${this.config.cpu} and utid != 0
+    `);
+
+    const rawResult = await this.query(`
+      select max(dur), count(1)
+      from ${this.tableName('sched')}
+    `);
+    this.maxDurNs = rawResult.columns[0].longValues![0];
+
+    const rowCount = rawResult.columns[1].longValues![0];
+    const bucketNs = this.cachedBucketSizeNs(rowCount);
+    if (bucketNs === undefined) {
+      return;
+    }
+    await this.query(`
+      create table ${this.tableName('sched_cached')} as
+      select
+        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
+        ts,
+        max(dur) as dur,
+        utid,
+        id
+      from ${this.tableName('sched')}
+      group by cached_tsq
+      order by cached_tsq
+    `);
+    this.cachedBucketNs = bucketNs;
+  }
+
   async onBoundsChange(start: number, end: number, resolution: number):
       Promise<Data> {
+    const resolutionNs = toNs(resolution);
+
+    // The resolution should always be a power of two for the logic of this
+    // function to make sense.
+    assertTrue(Math.log2(resolutionNs) % 1 === 0);
+
     const 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.round(resolution * 1e9 * pxSize / 2) * 2;
+    const bucketNs =
+        Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
 
-    if (this.maxDurNs === 0) {
-      const query = `SELECT max(dur) FROM sched WHERE cpu = ${this.config.cpu}`;
-      const rawResult = await this.query(query);
-      if (rawResult.numRecords === 1) {
-        this.maxDurNs = rawResult.columns[0].longValues![0];
-      }
-    }
+    const isCached = this.cachedBucketNs <= bucketNs;
+    const queryTsq = isCached ?
+        `cached_tsq / ${bucketNs} * ${bucketNs}` :
+        `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+    const queryTable =
+        isCached ? this.tableName('sched_cached') : this.tableName('sched');
+    const constainColumn = isCached ? 'cached_tsq' : 'ts';
 
-    const query = `
-      SELECT
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+    const rawResult = await this.query(`
+      select
+        ${queryTsq} as tsq,
         ts,
-        max(dur),
+        max(dur) as dur,
         utid,
         id
-      FROM sched
-      WHERE cpu = ${this.config.cpu} AND 
-        ts >= (${startNs - this.maxDurNs}) AND
-        ts <= ${endNs} AND
-        utid != 0
-      GROUP BY tsq`;
-    const rawResult = await this.query(query);
+      from ${queryTable}
+      where
+        ${constainColumn} >= ${startNs - this.maxDurNs} and
+        ${constainColumn} <= ${endNs}
+      group by tsq
+      order by tsq
+    `);
 
-    const numRows = +rawResult.numRecords;
-    const slices: SliceData = {
-      kind: 'slice',
+    const numRows = slowlyCountRows(rawResult);
+    const slices: Data = {
       start,
       end,
       resolution,
@@ -94,6 +139,10 @@
 
     return slices;
   }
+
+  async onDestroy() {
+    await this.query(`drop table if exists ${this.tableName('sched_cached')}`);
+  }
 }
 
 trackControllerRegistry.register(CpuSliceTrackController);
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index cc210a1..b6ca8d7 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {search, searchEq} from '../../base/binary_search';
+import {search, searchEq, searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
 import {Actions} from '../../common/actions';
 import {cropText, drawDoubleHeadedArrow} from '../../common/canvas_utils';
+import {colorForThread} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
 import {timeToString} from '../../common/time';
 import {checkerboardExcept} from '../../frontend/checkerboard';
-import {colorForThread, hueForCpu} from '../../frontend/colorizer';
 import {globals} from '../../frontend/globals';
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
@@ -28,14 +28,11 @@
   Config,
   CPU_SLICE_TRACK_KIND,
   Data,
-  SliceData,
-  SummaryData
 } from './common';
 
 const MARGIN_TOP = 3;
 const RECT_HEIGHT = 24;
 const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT;
-const SUMMARY_HEIGHT = TRACK_HEIGHT - MARGIN_TOP;
 
 class CpuSliceTrack extends Track<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
@@ -44,12 +41,10 @@
   }
 
   private mouseXpos?: number;
-  private hue: number;
   private utidHoveredInThisTrack = -1;
 
   constructor(trackState: TrackState) {
     super(trackState);
-    this.hue = hueForCpu(this.config.cpu);
   }
 
   getHeight(): number {
@@ -73,40 +68,10 @@
         timeScale.timeToPx(data.start),
         timeScale.timeToPx(data.end));
 
-    if (data.kind === 'summary') {
-      this.renderSummary(ctx, data);
-    } else if (data.kind === 'slice') {
-      this.renderSlices(ctx, data);
-    }
+    this.renderSlices(ctx, data);
   }
 
-  renderSummary(ctx: CanvasRenderingContext2D, data: SummaryData): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
-    const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start));
-    const bottomY = TRACK_HEIGHT;
-
-    let lastX = startPx;
-    let lastY = bottomY;
-
-    ctx.fillStyle = `hsl(${this.hue}, 50%, 60%)`;
-    ctx.beginPath();
-    ctx.moveTo(lastX, lastY);
-    for (let i = 0; i < data.utilizations.length; i++) {
-      const utilization = data.utilizations[i];
-      const startTime = i * data.bucketSizeSeconds + data.start;
-
-      lastX = Math.floor(timeScale.timeToPx(startTime));
-
-      ctx.lineTo(lastX, lastY);
-      lastY = MARGIN_TOP + Math.round(SUMMARY_HEIGHT * (1 - utilization));
-      ctx.lineTo(lastX, lastY);
-    }
-    ctx.lineTo(lastX, bottomY);
-    ctx.closePath();
-    ctx.fill();
-  }
-
-  renderSlices(ctx: CanvasRenderingContext2D, data: SliceData): void {
+  renderSlices(ctx: CanvasRenderingContext2D, data: Data): void {
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
     assertTrue(data.starts.length === data.ends.length);
     assertTrue(data.starts.length === data.utids.length);
@@ -115,36 +80,24 @@
     ctx.font = '12px Roboto Condensed';
     const charWidth = ctx.measureText('dbpqaouk').width / 8;
 
-    for (let i = 0; i < data.starts.length; i++) {
+    const rawStartIdx =
+        data.ends.findIndex(end => end >= visibleWindowTime.start);
+    const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
+
+    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+    const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
+
+    for (let i = startIdx; i < endIdx; i++) {
       const tStart = data.starts[i];
       const tEnd = data.ends[i];
       const utid = data.utids[i];
-      if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
-        continue;
-      }
+
       const rectStart = timeScale.timeToPx(tStart);
       const rectEnd = timeScale.timeToPx(tEnd);
       const rectWidth = Math.max(1, rectEnd - rectStart);
-      const threadInfo = globals.threads.get(utid);
 
-      // TODO: consider de-duplicating this code with the copied one from
-      // chrome_slices/frontend.ts.
-      let title = `[utid:${utid}]`;
-      let subTitle = '';
-      let pid = -1;
-      if (threadInfo) {
-        if (threadInfo.pid) {
-          pid = threadInfo.pid;
-          let procName = threadInfo.procName || '';
-          if (procName.startsWith('/')) {  // Remove folder paths from name
-            procName = procName.substring(procName.lastIndexOf('/') + 1);
-          }
-          title = `${procName} [${threadInfo.pid}]`;
-          subTitle = `${threadInfo.threadName} [${threadInfo.tid}]`;
-        } else {
-          title = `${threadInfo.threadName} [${threadInfo.tid}]`;
-        }
-      }
+      const threadInfo = globals.threads.get(utid);
+      const pid = threadInfo && threadInfo.pid ? threadInfo.pid : -1;
 
       const isHovering = globals.frontendLocalState.hoveredUtid !== -1;
       const isThreadHovered = globals.frontendLocalState.hoveredUtid === utid;
@@ -168,6 +121,22 @@
       // Don't render text when we have less than 5px to play with.
       if (rectWidth < 5) continue;
 
+      // TODO: consider de-duplicating this code with the copied one from
+      // chrome_slices/frontend.ts.
+      let title = `[utid:${utid}]`;
+      let subTitle = '';
+      if (threadInfo) {
+        if (threadInfo.pid) {
+          let procName = threadInfo.procName || '';
+          if (procName.startsWith('/')) {  // Remove folder paths from name
+            procName = procName.substring(procName.lastIndexOf('/') + 1);
+          }
+          title = `${procName} [${threadInfo.pid}]`;
+          subTitle = `${threadInfo.threadName} [${threadInfo.tid}]`;
+        } else {
+          title = `${threadInfo.threadName} [${threadInfo.tid}]`;
+        }
+      }
       title = cropText(title, charWidth, rectWidth);
       subTitle = cropText(subTitle, charWidth, rectWidth);
       const rectXCenter = rectStart + rectWidth / 2;
@@ -257,7 +226,7 @@
   onMouseMove({x, y}: {x: number, y: number}) {
     const data = this.data();
     this.mouseXpos = x;
-    if (data === undefined || data.kind === 'summary') return;
+    if (data === undefined) return;
     const {timeScale} = globals.frontendLocalState;
     if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
       this.utidHoveredInThisTrack = -1;
@@ -290,7 +259,7 @@
 
   onMouseClick({x}: {x: number}) {
     const data = this.data();
-    if (data === undefined || data.kind === 'summary') return false;
+    if (data === undefined) return false;
     const {timeScale} = globals.frontendLocalState;
     const time = timeScale.pxToTime(x);
     const index = search(data.starts, time);
diff --git a/ui/src/tracks/debug_slices/common.ts b/ui/src/tracks/debug_slices/common.ts
new file mode 100644
index 0000000..9625d54
--- /dev/null
+++ b/ui/src/tracks/debug_slices/common.ts
@@ -0,0 +1,22 @@
+// 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.
+// import {Data as ChromeSlicesData} from '../chrome_slices/common';
+
+export const DEBUG_SLICE_TRACK_KIND = 'DebugSliceTrack';
+
+export interface Config {
+  maxDepth: number;
+}
+
+export {Data} from '../chrome_slices/common';
diff --git a/ui/src/tracks/debug_slices/controller.ts b/ui/src/tracks/debug_slices/controller.ts
new file mode 100644
index 0000000..6471de0
--- /dev/null
+++ b/ui/src/tracks/debug_slices/controller.ts
@@ -0,0 +1,101 @@
+// 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.
+
+import {assertTrue} from '../../base/logging';
+import {Actions} from '../../common/actions';
+import {slowlyCountRows} from '../../common/query_iterator';
+import {fromNs, toNs} from '../../common/time';
+import {globals} from '../../controller/globals';
+import {
+  TrackController,
+  trackControllerRegistry,
+} from '../../controller/track_controller';
+
+import {Config, Data, DEBUG_SLICE_TRACK_KIND} from './common';
+
+class DebugSliceTrackController extends TrackController<Config, Data> {
+  static readonly kind = DEBUG_SLICE_TRACK_KIND;
+
+  async onReload() {
+    const rawResult = await this.query(`select max(depth) from debug_slices`);
+    const maxDepth = (slowlyCountRows(rawResult) === 0) ?
+        1 :
+        rawResult.columns[0].longValues![0];
+    globals.dispatch(
+        Actions.updateTrackConfig({id: this.trackId, config: {maxDepth}}));
+  }
+
+  async onBoundsChange(start: number, end: number, resolution: number):
+      Promise<Data> {
+    const rawResult = await this.query(
+        `select id, name, ts, dur, depth from debug_slices where
+        (ts + dur) >= ${toNs(start)} and ts <= ${toNs(end)}`);
+
+    assertTrue(rawResult.columns.length === 5);
+    const [idCol, nameCol, tsCol, durCol, depthCol] = rawResult.columns;
+    const idValues = idCol.longValues! || idCol.doubleValues!;
+    const tsValues = tsCol.longValues! || tsCol.doubleValues!;
+    const durValues = durCol.longValues! || durCol.doubleValues!;
+
+    const numRows = slowlyCountRows(rawResult);
+    const slices: Data = {
+      start,
+      end,
+      resolution,
+      length: numRows,
+      strings: [],
+      sliceIds: new Float64Array(numRows),
+      starts: new Float64Array(numRows),
+      ends: new Float64Array(numRows),
+      depths: new Uint16Array(numRows),
+      titles: new Uint16Array(numRows),
+      isInstant: new Uint16Array(numRows),
+      isIncomplete: new Uint16Array(numRows),
+    };
+
+    const stringIndexes = new Map<string, number>();
+    function internString(str: string) {
+      let idx = stringIndexes.get(str);
+      if (idx !== undefined) return idx;
+      idx = slices.strings.length;
+      slices.strings.push(str);
+      stringIndexes.set(str, idx);
+      return idx;
+    }
+
+    for (let i = 0; i < slowlyCountRows(rawResult); i++) {
+      let sliceStart: number, sliceEnd: number;
+      if (tsCol.isNulls![i] || durCol.isNulls![i]) {
+        sliceStart = sliceEnd = -1;
+      } else {
+        sliceStart = tsValues[i];
+        const sliceDur = durValues[i];
+        sliceEnd = sliceStart + sliceDur;
+      }
+      slices.sliceIds[i] = idCol.isNulls![i] ? -1 : idValues[i];
+      slices.starts[i] = fromNs(sliceStart);
+      slices.ends[i] = fromNs(sliceEnd);
+      slices.depths[i] = depthCol.isNulls![i] ? 0 : depthCol.longValues![i];
+      const sliceName =
+          nameCol.isNulls![i] ? '[null]' : nameCol.stringValues![i];
+      slices.titles[i] = internString(sliceName);
+      slices.isInstant[i] = 0;
+      slices.isIncomplete[i] = 0;
+    }
+
+    return slices;
+  }
+}
+
+trackControllerRegistry.register(DebugSliceTrackController);
diff --git a/ui/src/tracks/debug_slices/frontend.ts b/ui/src/tracks/debug_slices/frontend.ts
new file mode 100644
index 0000000..bdda433
--- /dev/null
+++ b/ui/src/tracks/debug_slices/frontend.ts
@@ -0,0 +1,55 @@
+// 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.
+
+import * as m from 'mithril';
+
+import {Actions} from '../../common/actions';
+import {TrackState} from '../../common/state';
+import {globals} from '../../frontend/globals';
+import {Track} from '../../frontend/track';
+import {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
+import {trackRegistry} from '../../frontend/track_registry';
+import {ChromeSliceTrack} from '../chrome_slices/frontend';
+
+import {DEBUG_SLICE_TRACK_KIND} from './common';
+
+export class DebugSliceTrack extends ChromeSliceTrack {
+  static readonly kind = DEBUG_SLICE_TRACK_KIND;
+  static create(trackState: TrackState): Track {
+    return new DebugSliceTrack(trackState);
+  }
+
+  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
+    const buttons: Array<m.Vnode<TrackButtonAttrs>> = [];
+    buttons.push(m(TrackButton, {
+      action: () => {
+        globals.dispatch(Actions.requestTrackReload({}));
+      },
+      i: 'refresh',
+      tooltip: 'Refresh tracks',
+      showButton: true,
+    }));
+    buttons.push(m(TrackButton, {
+      action: () => {
+        globals.dispatch(Actions.removeDebugTrack({}));
+      },
+      i: 'close',
+      tooltip: 'Close',
+      showButton: true,
+    }));
+    return buttons;
+  }
+}
+
+trackRegistry.register(DebugSliceTrack);
diff --git a/ui/src/tracks/heap_profile/controller.ts b/ui/src/tracks/heap_profile/controller.ts
index bab0b15..2422af1 100644
--- a/ui/src/tracks/heap_profile/controller.ts
+++ b/ui/src/tracks/heap_profile/controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {
   TrackController,
   trackControllerRegistry
@@ -45,7 +46,7 @@
         select distinct(graph_sample_ts) as ts, 'graph' as type from
         heap_graph_object
         where upid = ${this.config.upid}) order by ts`);
-    const numRows = +result.numRecords;
+    const numRows = slowlyCountRows(result);
     const data: Data = {
       start,
       end,
diff --git a/ui/src/tracks/heap_profile/frontend.ts b/ui/src/tracks/heap_profile/frontend.ts
index 86d177e..c9c7466 100644
--- a/ui/src/tracks/heap_profile/frontend.ts
+++ b/ui/src/tracks/heap_profile/frontend.ts
@@ -16,10 +16,7 @@
 import {Actions} from '../../common/actions';
 import {TrackState} from '../../common/state';
 import {fromNs, toNs} from '../../common/time';
-import {
-  HEAP_PROFILE_COLOR,
-  HEAP_PROFILE_HOVERED_COLOR
-} from '../../frontend/flamegraph';
+import {HEAP_PROFILE_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
 import {TimeScale} from '../../frontend/time_scale';
 import {Track} from '../../frontend/track';
@@ -27,6 +24,8 @@
 
 import {Config, Data, HEAP_PROFILE_TRACK_KIND} from './common';
 
+const HEAP_PROFILE_COLOR = 'hsl(224, 45%, 70%)';
+
 // 0.5 Makes the horizontal lines sharp.
 const MARGIN_TOP = 4.5;
 const RECT_HEIGHT = 30.5;
@@ -119,8 +118,6 @@
     if (index !== -1) {
       const ts = data.tsStarts[index];
       const type = data.types[index];
-      globals.dispatch(Actions.showHeapProfileFlamegraph(
-          {id: index, upid: this.config.upid, ts, type}));
       globals.makeSelection(Actions.selectHeapProfile(
           {id: index, upid: this.config.upid, ts, type}));
       return true;
diff --git a/ui/src/tracks/process_scheduling/controller.ts b/ui/src/tracks/process_scheduling/controller.ts
index c763e85..c7676ed 100644
--- a/ui/src/tracks/process_scheduling/controller.ts
+++ b/ui/src/tracks/process_scheduling/controller.ts
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 import {assertTrue} from '../../base/logging';
+import {RawQueryResult} from '../../common/protos';
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
 import {
   TrackController,
@@ -29,60 +31,66 @@
 // associated with them.
 class ProcessSchedulingTrackController extends TrackController<Config, Data> {
   static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
-  private setup = false;
-  private maxDurNs = 0;
+
   private maxCpu = 0;
+  private maxDurNs = 0;
+  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
-      Promise<Data> {
-    if (this.config.upid === null) {
-      throw new Error('Upid not set.');
+  async onSetup() {
+    await this.createSchedView();
+
+    const cpus = await this.engine.getCpus();
+
+    // A process scheduling track should only exist in a trace that has cpus.
+    assertTrue(cpus.length > 0);
+    this.maxCpu = Math.max(...cpus) + 1;
+
+    const result = await this.query(`
+      select max(dur), count(1)
+      from ${this.tableName('process_sched')}
+    `);
+    this.maxDurNs = result.columns[0].longValues![0];
+
+    const rowCount = result.columns[1].longValues![0];
+    const bucketNs = this.cachedBucketSizeNs(rowCount);
+    if (bucketNs === undefined) {
+      return;
     }
-
-    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.round(resolution * 1e9 * pxSize / 2) * 2;
-
-    if (this.setup === false) {
-      const cpus = await this.engine.getCpus();
-      // A process scheduling track should only exist in a trace that has cpus.
-      assertTrue(cpus.length > 0);
-      this.maxCpu = Math.max(...cpus) + 1;
-
-      const maxDurResult = await this.query(`select max(dur)
-        from sched
-        join thread using(utid)
-        where
-          utid != 0 and
-          upid = ${this.config.upid}`);
-      if (maxDurResult.numRecords === 1) {
-        this.maxDurNs = maxDurResult.columns[0].longValues![0];
-      }
-
-      this.setup = true;
-    }
-
-    const rawResult = await this.query(`select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+    await this.query(`
+      create table ${this.tableName('process_sched_cached')} as
+      select
+        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
         ts,
         max(dur) as dur,
         cpu,
         utid
-      from sched
-      join thread using(utid)
-      where
-        ts >= ${startNs - this.maxDurNs} and
-        ts <= ${endNs} and
-        utid != 0 and
-        upid = ${this.config.upid}
-      group by cpu, tsq`);
+      from ${this.tableName('process_sched')}
+      group by cached_tsq, cpu
+      order by cached_tsq, cpu
+    `);
+    this.cachedBucketNs = bucketNs;
+  }
 
-    const numRows = +rawResult.numRecords;
+  async onBoundsChange(start: number, end: number, resolution: number):
+      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);
+
+    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 rawResult = await this.queryData(startNs, endNs, bucketNs);
+
+    const numRows = slowlyCountRows(rawResult);
     const slices: Data = {
       kind: 'slice',
       start,
@@ -118,6 +126,41 @@
     }
     return slices;
   }
+
+  private queryData(startNs: number, endNs: number, bucketNs: number):
+      Promise<RawQueryResult> {
+    const isCached = this.cachedBucketNs <= bucketNs;
+    const tsq = isCached ? `cached_tsq / ${bucketNs} * ${bucketNs}` :
+                           `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+    const queryTable = isCached ? this.tableName('process_sched_cached') :
+                                  this.tableName('process_sched');
+    const constainColumn = isCached ? 'cached_tsq' : 'ts';
+    return this.query(`
+      select
+        ${tsq} as tsq,
+        ts,
+        max(dur) as dur,
+        cpu,
+        utid
+      from ${queryTable}
+      where
+        ${constainColumn} >= ${startNs - this.maxDurNs} and
+        ${constainColumn} <= ${endNs}
+      group by tsq, cpu
+      order by tsq, cpu
+    `);
+  }
+
+  private async createSchedView() {
+    await this.query(`
+      create view ${this.tableName('process_sched')} as
+      select ts, dur, cpu, utid
+      from experimental_sched_upid
+      where
+        utid != 0 and
+        upid = ${this.config.upid}
+    `);
+  }
 }
 
 trackControllerRegistry.register(ProcessSchedulingTrackController);
diff --git a/ui/src/tracks/process_scheduling/frontend.ts b/ui/src/tracks/process_scheduling/frontend.ts
index d7a5e09..e7c4eb1 100644
--- a/ui/src/tracks/process_scheduling/frontend.ts
+++ b/ui/src/tracks/process_scheduling/frontend.ts
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {searchEq, searchRange} from '../../base/binary_search';
+import {searchEq, searchRange, searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
+import {colorForThread} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
 import {checkerboardExcept} from '../../frontend/checkerboard';
-import {colorForThread} from '../../frontend/colorizer';
 import {globals} from '../../frontend/globals';
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
@@ -68,16 +68,21 @@
     assertTrue(data.starts.length === data.ends.length);
     assertTrue(data.starts.length === data.utids.length);
 
+    const rawStartIdx =
+        data.ends.findIndex(end => end >= visibleWindowTime.start);
+    const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx;
+
+    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+    const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
+
     const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
 
-    for (let i = 0; i < data.starts.length; i++) {
+    for (let i = startIdx; i < endIdx; i++) {
       const tStart = data.starts[i];
       const tEnd = data.ends[i];
       const utid = data.utids[i];
       const cpu = data.cpus[i];
-      if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
-        continue;
-      }
+
       const rectStart = timeScale.timeToPx(tStart);
       const rectEnd = timeScale.timeToPx(tEnd);
       const rectWidth = rectEnd - rectStart;
diff --git a/ui/src/tracks/process_summary/controller.ts b/ui/src/tracks/process_summary/controller.ts
index fa6c98d..75242fe 100644
--- a/ui/src/tracks/process_summary/controller.ts
+++ b/ui/src/tracks/process_summary/controller.ts
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {slowlyCountRows} from '../../common/query_iterator';
 import {fromNs, toNs} from '../../common/time';
 import {LIMIT} from '../../common/track_data';
-
 import {
   TrackController,
   trackControllerRegistry
@@ -99,7 +99,7 @@
       limit ${LIMIT}`;
 
     const rawResult = await this.query(query);
-    const numRows = +rawResult.numRecords;
+    const numRows = slowlyCountRows(rawResult);
 
     const summary: Data = {
       start,
diff --git a/ui/src/tracks/process_summary/frontend.ts b/ui/src/tracks/process_summary/frontend.ts
index 9e44b8a..0a259ca 100644
--- a/ui/src/tracks/process_summary/frontend.ts
+++ b/ui/src/tracks/process_summary/frontend.ts
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {colorForTid} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
 import {checkerboardExcept} from '../../frontend/checkerboard';
-import {colorForTid} from '../../frontend/colorizer';
 import {globals} from '../../frontend/globals';
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
diff --git a/ui/src/tracks/thread_state/common.ts b/ui/src/tracks/thread_state/common.ts
index 6c21344..9bede66 100644
--- a/ui/src/tracks/thread_state/common.ts
+++ b/ui/src/tracks/thread_state/common.ts
@@ -18,15 +18,13 @@
 
 export interface Data extends TrackData {
   strings: string[];
+  ids: Float64Array;
   starts: Float64Array;
   ends: Float64Array;
+  cpu: Int8Array;
   state: Uint16Array;  // Index into |strings|.
-  cpu: Uint8Array;
-  summarisedStateBreakdowns: Map<number, StatePercent>;
 }
 
-export type StatePercent = Map<string, number>;
-
 export interface Config {
   utid: number;
 }
diff --git a/ui/src/tracks/thread_state/controller.ts b/ui/src/tracks/thread_state/controller.ts
index 521da7e..a4718c6 100644
--- a/ui/src/tracks/thread_state/controller.ts
+++ b/ui/src/tracks/thread_state/controller.ts
@@ -12,9 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {assertFalse} from '../../base/logging';
+import {
+  iter,
+  NUM,
+  NUM_NULL,
+  slowlyCountRows,
+  STR_NULL
+} from '../../common/query_iterator';
+import {translateState} from '../../common/thread_state';
 import {fromNs, toNs} from '../../common/time';
-import {LIMIT} from '../../common/track_data';
-
 import {
   TrackController,
   trackControllerRegistry
@@ -28,125 +35,125 @@
 
 class ThreadStateTrackController extends TrackController<Config, Data> {
   static readonly kind = THREAD_STATE_TRACK_KIND;
-  private setup = false;
+
+  private maxDurNs = 0;
+
+  async onSetup() {
+    await this.query(`
+      create view ${this.tableName('thread_state')} as
+      select
+        id,
+        ts,
+        dur,
+        cpu,
+        state,
+        io_wait
+      from thread_state
+      where utid = ${this.config.utid} and utid != 0
+    `);
+
+    const rawResult = await this.query(`
+      select max(dur)
+      from ${this.tableName('thread_state')}
+    `);
+    this.maxDurNs = rawResult.columns[0].longValues![0];
+  }
 
   async onBoundsChange(start: number, end: number, resolution: number):
       Promise<Data> {
+    const resolutionNs = toNs(resolution);
     const startNs = toNs(start);
     const endNs = toNs(end);
-    const minNs = Math.round(resolution * 1e9);
 
-    await this.query(
-        `drop view if exists ${this.tableName('grouped_thread_states')}`);
+    // 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);
 
-    // This query gives all contiguous slices less than minNs the same grouping.
-    await this.query(`create view ${this.tableName('grouped_thread_states')} as
-    select ts, dur, cpu, state,
-    ifnull(sum(value) over (order by ts), 0) as grouping
-    from
-    (select *,
-    (dur >= ${minNs}) or lag(dur >= ${minNs}) over (order by ts) as value
-    from thread_state
-    where utid = ${this.config.utid})`);
-
-    // Since there are more rows than slices we will output, check the number of
-    // distinct groupings to find the number of slices.
-    const totalSlicesQuery = `select count(distinct(grouping))
-      from ${this.tableName('grouped_thread_states')}
-      where ts <= ${endNs} and ts + dur >= ${startNs}`;
-    const totalSlices = (await this.engine.queryOneRow(totalSlicesQuery))[0];
-
-    // We have ts contraints instead of span joining with the window table
-    // because when selecting a slice we need the real duration (even if it
-    // is outside of the current viewport)
-    // TODO(b/149303809): Return this to using
-    // the window table if possible.
-    const query = `select min(min(ts)) over (partition by grouping) as ts,
-    sum(sum(dur)) over (partition by grouping) as slice_dur,
-    cpu,
-    state,
-    (sum(dur) * 1.0)/(sum(sum(dur)) over (partition by grouping)) as percent,
-    grouping
-    from ${this.tableName('grouped_thread_states')}
-    where ts <= ${endNs} and ts + dur >= ${startNs}
-    group by grouping, state
-    order by grouping
-    limit ${LIMIT}`;
+    const query = `
+      select
+        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        ts,
+        max(dur) as dur,
+        cast(cpu as integer) as cpu,
+        state,
+        io_wait,
+        id
+      from ${this.tableName('thread_state')}
+      where
+        ts >= ${startNs - this.maxDurNs} and
+        ts <= ${endNs}
+      group by tsq, state, io_wait
+      order by tsq, state, io_wait
+    `;
 
     const result = await this.query(query);
-    const numRows = +result.numRecords;
+    const numRows = slowlyCountRows(result);
 
-    const summary: Data = {
+    const data: Data = {
       start,
       end,
       resolution,
-      length: totalSlices,
-      starts: new Float64Array(totalSlices),
-      ends: new Float64Array(totalSlices),
+      length: numRows,
+      ids: new Float64Array(numRows),
+      starts: new Float64Array(numRows),
+      ends: new Float64Array(numRows),
       strings: [],
-      state: new Uint16Array(totalSlices),
-      cpu: new Uint8Array(totalSlices),
-      summarisedStateBreakdowns: new Map(),
+      state: new Uint16Array(numRows),
+      cpu: new Int8Array(numRows),
     };
 
-    const stringIndexes = new Map<string, number>();
-    function internString(str: string) {
-      let idx = stringIndexes.get(str);
+    const stringIndexes =
+        new Map<{shortState: string, ioWait: boolean | undefined}, number>();
+    function internState(shortState: string, ioWait: boolean|undefined) {
+      let idx = stringIndexes.get({shortState, ioWait});
       if (idx !== undefined) return idx;
-      idx = summary.strings.length;
-      summary.strings.push(str);
-      stringIndexes.set(str, idx);
+      idx = data.strings.length;
+      data.strings.push(translateState(shortState, ioWait));
+      stringIndexes.set({shortState, ioWait}, idx);
       return idx;
     }
-
-    let outIndex = 0;
+    iter(
+        {
+          'ts': NUM,
+          'dur': NUM,
+          'cpu': NUM_NULL,
+          'state': STR_NULL,
+          'io_wait': NUM_NULL,
+          'id': NUM_NULL,
+        },
+        result);
     for (let row = 0; row < numRows; row++) {
       const cols = result.columns;
-      const start = +cols[0].longValues![row];
-      const dur = +cols[1].longValues![row];
-      const state = cols[3].stringValues![row];
-      const percent = +(cols[4].doubleValues![row].toFixed(2));
-      const grouping = +cols[5].longValues![row];
+      const startNsQ = +cols[0].longValues![row];
+      const startNs = +cols[1].longValues![row];
+      const durNs = +cols[2].longValues![row];
+      const endNs = startNs + durNs;
 
-      if (percent !== 1) {
-        let breakdownMap = summary.summarisedStateBreakdowns.get(outIndex);
-        if (!breakdownMap) {
-          breakdownMap = new Map();
-          summary.summarisedStateBreakdowns.set(outIndex, breakdownMap);
-        }
+      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
+      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
 
-        const currentPercent = breakdownMap.get(state);
-        if (currentPercent === undefined) {
-          breakdownMap.set(state, percent);
-        } else {
-          breakdownMap.set(state, currentPercent + percent);
-        }
-      }
+      const cpu = cols[3].isNulls![row] ? -1 : cols[3].longValues![row];
+      const state = cols[4].stringValues![row];
+      const ioWait =
+          cols[5].isNulls![row] ? undefined : !!cols[5].longValues![row];
+      const id = cols[6].isNulls![row] ? -1 : cols[6].longValues![row];
 
-      const nextGrouping =
-          row + 1 < numRows ? +cols[5].longValues![row + 1] : -1;
-      // If the next grouping is different then we have reached the end of this
-      // slice.
-      if (grouping !== nextGrouping) {
-        const numStates = summary.summarisedStateBreakdowns.get(outIndex) ?
-            summary.summarisedStateBreakdowns.get(outIndex)!.entries.length :
-            1;
-        summary.starts[outIndex] = fromNs(start);
-        summary.ends[outIndex] = fromNs(start + dur);
-        summary.state[outIndex] =
-            internString(numStates === 1 ? state : 'Various states');
-        summary.cpu[outIndex] = +cols[2].longValues![row];
-        outIndex++;
-      }
+      // We should never have the end timestamp being the same as the bucket
+      // start.
+      assertFalse(startNsQ === endNsQ);
+
+      data.starts[row] = fromNs(startNsQ);
+      data.ends[row] = fromNs(endNsQ);
+      data.state[row] = internState(state, ioWait);
+      data.ids[row] = id;
+      data.cpu[row] = cpu;
     }
-    return summary;
+    return data;
   }
 
-  onDestroy(): void {
-    if (this.setup) {
-      this.query(`drop view ${this.tableName('grouped_thread_states')}`);
-      this.setup = false;
-    }
+  async onDestroy() {
+    await this.query(`drop table if exists ${this.tableName('thread_state')}`);
   }
 }
 
diff --git a/ui/src/tracks/thread_state/frontend.ts b/ui/src/tracks/thread_state/frontend.ts
index 7410a0d..0383010 100644
--- a/ui/src/tracks/thread_state/frontend.ts
+++ b/ui/src/tracks/thread_state/frontend.ts
@@ -12,13 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {search, searchEq} from '../../base/binary_search';
+import {search} from '../../base/binary_search';
 import {Actions} from '../../common/actions';
 import {cropText} from '../../common/canvas_utils';
+import {colorForState} from '../../common/colorizer';
 import {TrackState} from '../../common/state';
-import {translateState} from '../../common/thread_state';
 import {checkerboardExcept} from '../../frontend/checkerboard';
-import {Color, colorForState} from '../../frontend/colorizer';
 import {globals} from '../../frontend/globals';
 import {Track} from '../../frontend/track';
 import {trackRegistry} from '../../frontend/track_registry';
@@ -26,7 +25,6 @@
 import {
   Config,
   Data,
-  StatePercent,
   THREAD_STATE_TRACK_KIND,
 } from './common';
 
@@ -34,20 +32,6 @@
 const RECT_HEIGHT = 14;
 const EXCESS_WIDTH = 10;
 
-function groupBusyStates(resolution: number) {
-  return resolution >= 0.0001;
-}
-
-function getSummarizedSliceText(breakdownMap: StatePercent) {
-  let result = 'Various (';
-  const sorted =
-      new Map([...breakdownMap.entries()].sort((a, b) => b[1] - a[1]));
-  for (const [state, value] of sorted.entries()) {
-    result += `${state}: ${Math.round(value * 100)}%, `;
-  }
-  return result.slice(0, result.length - 2) + ')';
-}
-
 class ThreadStateTrack extends Track<Config, Data> {
   static readonly kind = THREAD_STATE_TRACK_KIND;
   static create(trackState: TrackState): ThreadStateTrack {
@@ -69,6 +53,10 @@
 
     if (data === undefined) return;  // Can't possibly draw anything.
 
+    // The draw of the rect on the selected slice must happen after the other
+    // drawings, otherwise it would result under another rect.
+    let drawRectOnSelected = () => {};
+
     checkerboardExcept(
         ctx,
         this.getHeight(),
@@ -78,8 +66,6 @@
         timeScale.timeToPx(data.end),
     );
 
-    const shouldGroupBusyStates = groupBusyStates(data.resolution);
-
     ctx.textAlign = 'center';
     ctx.font = '10px Roboto Condensed';
 
@@ -90,95 +76,56 @@
       if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
         continue;
       }
-      if (tStart && tEnd) {
-        // Don't display a slice for Task Dead.
-        if (state === 'x') continue;
-        const rectStart = timeScale.timeToPx(tStart);
-        const rectEnd = timeScale.timeToPx(tEnd);
-        let rectWidth = rectEnd - rectStart;
-        const color = colorForState(state);
-        let text = translateState(state);
-        const breakdown = data.summarisedStateBreakdowns.get(i);
-        if (breakdown) {
-          colorSummarizedSlice(breakdown, rectStart, rectEnd);
-          text = getSummarizedSliceText(breakdown);
-        } else {
-          let colorStr = `hsl(${color.h},${color.s}%,${color.l}%)`;
-          if (color.a) {
-            colorStr = `hsla(${color.h},${color.s}%,${color.l}%, ${color.a})`;
-          }
-          ctx.fillStyle = colorStr;
-        }
-        if (shouldGroupBusyStates && rectWidth < 1) {
-          rectWidth = 1;
-        }
-        ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
 
-        // Don't render text when we have less than 10px to play with.
-        if (rectWidth < 10 || text === 'Sleeping') continue;
-        const title = cropText(text, charWidth, rectWidth);
-        const rectXCenter = rectStart + rectWidth / 2;
-        ctx.fillStyle = color.l > 80 || breakdown ? '#404040' : '#fff';
-        ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 3);
+      // Don't display a slice for Task Dead.
+      if (state === 'x') continue;
+      const rectStart = timeScale.timeToPx(tStart);
+      const rectEnd = timeScale.timeToPx(tEnd);
+
+      const currentSelection = globals.state.currentSelection;
+      const isSelected = currentSelection &&
+          currentSelection.kind === 'THREAD_STATE' &&
+          currentSelection.id === data.ids[i];
+
+      const color = colorForState(state);
+
+      let colorStr = `hsl(${color.h},${color.s}%,${color.l}%)`;
+      if (color.a) {
+        colorStr = `hsla(${color.h},${color.s}%,${color.l}%, ${color.a})`;
+      }
+      ctx.fillStyle = colorStr;
+
+      const rectWidth = rectEnd - rectStart;
+      ctx.fillRect(rectStart, MARGIN_TOP, rectWidth, RECT_HEIGHT);
+
+      // Don't render text when we have less than 10px to play with.
+      if (rectWidth < 10 || state === 'Sleeping') continue;
+      const title = cropText(state, charWidth, rectWidth);
+      const rectXCenter = rectStart + rectWidth / 2;
+      ctx.fillStyle = color.l > 80 ? '#404040' : '#fff';
+      ctx.fillText(title, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 3);
+
+      if (isSelected) {
+        drawRectOnSelected = () => {
+          const rectStart =
+              Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
+          const rectEnd = Math.min(
+              timeScale.timeToPx(visibleWindowTime.end) + EXCESS_WIDTH,
+              timeScale.timeToPx(tEnd));
+          const color = colorForState(state);
+          ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
+          ctx.beginPath();
+          ctx.lineWidth = 3;
+          ctx.strokeRect(
+              rectStart,
+              MARGIN_TOP - 1.5,
+              rectEnd - rectStart,
+              RECT_HEIGHT + 3);
+          ctx.closePath();
+        };
       }
     }
-
-    const selection = globals.state.currentSelection;
-    if (selection !== null && selection.kind === 'THREAD_STATE' &&
-        selection.utid === this.config.utid) {
-      const [startIndex, endIndex] = searchEq(data.starts, selection.ts);
-      if (startIndex !== endIndex) {
-        const tStart = data.starts[startIndex];
-        const tEnd = data.ends[startIndex];
-        const state = data.strings[data.state[startIndex]];
-
-        // If we try to draw too far off the end of the canvas (+/-4m~),
-        // the line is not drawn. Instead limit drawing to the canvas
-        // boundaries, but allow some excess to ensure that the start and end
-        // of the rect are not shown unless that is truly when it starts/ends.
-        const rectStart =
-            Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
-        const rectEnd = Math.min(
-            timeScale.timeToPx(visibleWindowTime.end) + EXCESS_WIDTH,
-            timeScale.timeToPx(tEnd));
-        const color = colorForState(state);
-        ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
-        ctx.beginPath();
-        ctx.lineWidth = 3;
-        ctx.strokeRect(
-            rectStart, MARGIN_TOP - 1.5, rectEnd - rectStart, RECT_HEIGHT + 3);
-        ctx.closePath();
-      }
-    }
-
-    // Make a gradient ordered most common to least based on the colors of the
-    // states within the summarized slice.
-    function colorSummarizedSlice(
-        breakdownMap: StatePercent, rectStart: number, rectEnd: number) {
-      const gradient =
-          ctx.createLinearGradient(rectStart, MARGIN_TOP, rectEnd, MARGIN_TOP);
-      // Sometimes multiple states have the same color e.g R, R+
-      const colorMap = new Map<Color, number>();
-      for (const [state, value] of breakdownMap.entries()) {
-        const color = colorForState(state);
-        const currentColorValue = colorMap.get(color);
-        if (currentColorValue === undefined) {
-          colorMap.set(color, value);
-        } else {
-          colorMap.set(color, currentColorValue + value);
-        }
-      }
-
-      const sorted =
-          new Map([...colorMap.entries()].sort((a, b) => b[1] - a[1]));
-      let colorStop = 0;
-      for (const [color, value] of sorted.entries()) {
-        const colorString = `hsl(${color.h},${color.s}%,${color.l}%)`;
-        colorStop = Math.max(0, Math.min(1, colorStop + value));
-        gradient.addColorStop(colorStop, colorString);
-      }
-      ctx.fillStyle = gradient;
-    }
+    drawRectOnSelected();
   }
 
   onMouseClick({x}: {x: number}) {
@@ -187,23 +134,12 @@
     const {timeScale} = globals.frontendLocalState;
     const time = timeScale.pxToTime(x);
     const index = search(data.starts, time);
-    const ts = index === -1 ? undefined : data.starts[index];
-    const tsEnd = index === -1 ? undefined : data.ends[index];
-    const state = index === -1 ? undefined : data.strings[data.state[index]];
-    const cpu = index === -1 ? undefined : data.cpu[index];
-    const utid = this.config.utid;
-    if (ts && state && tsEnd && cpu !== undefined) {
-      globals.makeSelection(Actions.selectThreadState({
-        utid,
-        ts,
-        dur: tsEnd - ts,
-        state,
-        cpu,
-        trackId: this.trackState.id
-      }));
-      return true;
-    }
-    return false;
+    if (index === -1) return false;
+
+    const id = data.ids[index];
+    globals.makeSelection(
+        Actions.selectThreadState({id, trackId: this.trackState.id}));
+    return true;
   }
 }