Merge "Add producer for Perfetto Java Heap Profiler."
diff --git a/Android.bp b/Android.bp
index d5c0aeb..e3c6e27 100644
--- a/Android.bp
+++ b/Android.bp
@@ -73,7 +73,9 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -89,7 +91,6 @@
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
     "src/base/metatrace.cc",
@@ -158,6 +159,13 @@
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/tracing_service_state.cc",
     "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/trace_writer_base.cc",
   ],
   shared_libs: [
@@ -167,9 +175,6 @@
     "libprotobuf-cpp-lite",
     "libunwindstack",
   ],
-  static_libs: [
-    "perfetto_src_tracing_ipc",
-  ],
   init_rc: [
     "heapprofd.rc",
   ],
@@ -195,7 +200,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -211,7 +218,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
@@ -299,7 +305,9 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -315,7 +323,6 @@
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     "src/android_internal/lazy_library_loader.cc",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
@@ -404,15 +411,19 @@
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/tracing_service_state.cc",
     "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/trace_writer_base.cc",
   ],
   shared_libs: [
     "liblog",
     "libprotobuf-cpp-lite",
   ],
-  static_libs: [
-    "perfetto_src_tracing_ipc",
-  ],
   export_include_dirs: [
     "include",
     "include/perfetto/base/build_configs/android_tree",
@@ -439,7 +450,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -455,7 +468,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
@@ -530,7 +542,9 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -546,7 +560,6 @@
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
     "src/base/metatrace.cc",
@@ -604,6 +617,13 @@
     "src/tracing/internal/in_process_tracing_backend.cc",
     "src/tracing/internal/system_tracing_backend.cc",
     "src/tracing/internal/tracing_muxer_impl.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/platform.cc",
     "src/tracing/platform_posix.cc",
     "src/tracing/trace_writer_base.cc",
@@ -614,9 +634,6 @@
   shared_libs: [
     "libprotobuf-cpp-lite",
   ],
-  static_libs: [
-    "perfetto_src_tracing_ipc",
-  ],
   export_include_dirs: [
     "include",
     "include/perfetto/base/build_configs/android_tree",
@@ -643,7 +660,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -659,7 +678,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   export_generated_headers: [
     "perfetto_protos_perfetto_common_lite_gen_headers",
@@ -683,7 +701,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -699,7 +719,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
@@ -735,7 +754,9 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -751,7 +772,6 @@
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     ":perfetto_src_perfetto_cmd_protos_gen",
     "src/android_internal/lazy_library_loader.cc",
     "src/base/event_fd.cc",
@@ -815,6 +835,13 @@
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/tracing_service_state.cc",
     "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/trace_writer_base.cc",
   ],
   shared_libs: [
@@ -822,9 +849,6 @@
     "libprotobuf-cpp-lite",
     "libz",
   ],
-  static_libs: [
-    "perfetto_src_tracing_ipc",
-  ],
   generated_headers: [
     "perfetto_protos_perfetto_common_lite_gen_headers",
     "perfetto_protos_perfetto_common_zero_gen_headers",
@@ -847,7 +871,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -863,7 +889,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
   defaults: [
@@ -930,8 +955,11 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_lite_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_lite_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
@@ -959,7 +987,6 @@
     ":perfetto_protos_perfetto_trace_track_event_lite_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     "src/android_internal/lazy_library_loader.cc",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
@@ -1070,6 +1097,13 @@
     "src/tracing/internal/in_process_tracing_backend.cc",
     "src/tracing/internal/system_tracing_backend.cc",
     "src/tracing/internal/tracing_muxer_impl.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/platform.cc",
     "src/tracing/platform_posix.cc",
     "src/tracing/test/api_test_support.cc",
@@ -1093,7 +1127,6 @@
   static_libs: [
     "libgmock",
     "libperfetto_client_experimental",
-    "perfetto_src_tracing_ipc",
   ],
   generated_headers: [
     "perfetto_protos_perfetto_common_lite_gen_headers",
@@ -1117,8 +1150,11 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
@@ -1146,7 +1182,6 @@
     "perfetto_protos_perfetto_trace_track_event_lite_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
   ],
   defaults: [
     "perfetto_defaults",
@@ -2013,6 +2048,40 @@
   ],
 }
 
+// GN target: //protos/perfetto/ipc:wire_protocol_gen
+genrule {
+  name: "perfetto_protos_perfetto_ipc_wire_protocol_gen",
+  srcs: [
+    "protos/perfetto/ipc/wire_protocol.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/ipc/wire_protocol.pb.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/ipc:wire_protocol_gen
+genrule {
+  name: "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
+  srcs: [
+    "protos/perfetto/ipc/wire_protocol.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/ipc/wire_protocol.pb.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
 // GN target: //protos/perfetto/metrics/android:zero_gen
 genrule {
   name: "perfetto_protos_perfetto_metrics_android_zero_gen",
@@ -2211,6 +2280,76 @@
   ],
 }
 
+// GN target: //protos/perfetto/trace/appended_data:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_appended_data_lite_gen",
+  srcs: [
+    "protos/perfetto/trace/appended_data/appended_data.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/appended_data/appended_data.pb.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/appended_data:lite_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/appended_data/appended_data.proto",
+  ],
+  tools: [
+    "aprotoc",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/appended_data/appended_data.pb.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/appended_data:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_appended_data_zero_gen",
+  srcs: [
+    "protos/perfetto/trace/appended_data/appended_data.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protozero_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protozero_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/appended_data/appended_data.pbzero.cc",
+  ],
+}
+
+// GN target: //protos/perfetto/trace/appended_data:zero_gen
+genrule {
+  name: "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
+  srcs: [
+    "protos/perfetto/trace/appended_data/appended_data.proto",
+  ],
+  tools: [
+    "aprotoc",
+    "perfetto_src_protozero_protoc_plugin_protozero_plugin___gn_standalone_toolchain_gcc_like_host_",
+  ],
+  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location perfetto_src_protozero_protoc_plugin_protozero_plugin___gn_standalone_toolchain_gcc_like_host_) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto $(in)",
+  out: [
+    "external/perfetto/protos/perfetto/trace/appended_data/appended_data.pbzero.h",
+  ],
+  export_include_dirs: [
+    ".",
+    "protos",
+  ],
+}
+
 // GN target: //protos/perfetto/trace/chrome:lite_gen
 genrule {
   name: "perfetto_protos_perfetto_trace_chrome_lite_gen",
@@ -2385,7 +2524,6 @@
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
-    "protos/perfetto/trace/ftrace/gpu.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
     "protos/perfetto/trace/ftrace/ipi.proto",
     "protos/perfetto/trace/ftrace/irq.proto",
@@ -2425,7 +2563,6 @@
     "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/generic.pb.cc",
-    "external/perfetto/protos/perfetto/trace/ftrace/gpu.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ipi.pb.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/irq.pb.cc",
@@ -2466,7 +2603,6 @@
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
-    "protos/perfetto/trace/ftrace/gpu.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
     "protos/perfetto/trace/ftrace/ipi.proto",
     "protos/perfetto/trace/ftrace/irq.proto",
@@ -2506,7 +2642,6 @@
     "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/generic.pb.h",
-    "external/perfetto/protos/perfetto/trace/ftrace/gpu.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ipi.pb.h",
     "external/perfetto/protos/perfetto/trace/ftrace/irq.pb.h",
@@ -2551,7 +2686,6 @@
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
-    "protos/perfetto/trace/ftrace/gpu.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
     "protos/perfetto/trace/ftrace/ipi.proto",
     "protos/perfetto/trace/ftrace/irq.proto",
@@ -2592,7 +2726,6 @@
     "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/generic.pbzero.cc",
-    "external/perfetto/protos/perfetto/trace/ftrace/gpu.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/ipi.pbzero.cc",
     "external/perfetto/protos/perfetto/trace/ftrace/irq.pbzero.cc",
@@ -2633,7 +2766,6 @@
     "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto",
     "protos/perfetto/trace/ftrace/ftrace_stats.proto",
     "protos/perfetto/trace/ftrace/generic.proto",
-    "protos/perfetto/trace/ftrace/gpu.proto",
     "protos/perfetto/trace/ftrace/i2c.proto",
     "protos/perfetto/trace/ftrace/ipi.proto",
     "protos/perfetto/trace/ftrace/irq.proto",
@@ -2674,7 +2806,6 @@
     "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/generic.pbzero.h",
-    "external/perfetto/protos/perfetto/trace/ftrace/gpu.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/i2c.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/ipi.pbzero.h",
     "external/perfetto/protos/perfetto/trace/ftrace/irq.pbzero.h",
@@ -3645,6 +3776,23 @@
 cc_binary_host {
   name: "perfetto_src_ipc_protoc_plugin_ipc_plugin___gn_standalone_toolchain_gcc_like_host_",
   srcs: [
+    "src/base/event_fd.cc",
+    "src/base/file_utils.cc",
+    "src/base/metatrace.cc",
+    "src/base/paged_memory.cc",
+    "src/base/pipe.cc",
+    "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
+    "src/base/string_view.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/uuid.cc",
+    "src/base/virtual_destructors.cc",
+    "src/base/waitable_event.cc",
+    "src/base/watchdog_posix.cc",
     "src/ipc/protoc_plugin/ipc_plugin.cc",
   ],
   shared_libs: [
@@ -3709,40 +3857,6 @@
   ],
 }
 
-// GN target: //src/ipc:wire_protocol_gen
-genrule {
-  name: "perfetto_src_ipc_wire_protocol_gen",
-  srcs: [
-    "src/ipc/wire_protocol.proto",
-  ],
-  tools: [
-    "aprotoc",
-  ],
-  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
-  out: [
-    "external/perfetto/src/ipc/wire_protocol.pb.cc",
-  ],
-}
-
-// GN target: //src/ipc:wire_protocol_gen
-genrule {
-  name: "perfetto_src_ipc_wire_protocol_gen_headers",
-  srcs: [
-    "src/ipc/wire_protocol.proto",
-  ],
-  tools: [
-    "aprotoc",
-  ],
-  cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto $(in)",
-  out: [
-    "external/perfetto/src/ipc/wire_protocol.pb.h",
-  ],
-  export_include_dirs: [
-    ".",
-    "protos",
-  ],
-}
-
 // GN target: //src/perfetto_cmd:protos_gen
 genrule {
   name: "perfetto_src_perfetto_cmd_protos_gen",
@@ -3781,6 +3895,23 @@
 cc_binary_host {
   name: "perfetto_src_protozero_protoc_plugin_protozero_plugin___gn_standalone_toolchain_gcc_like_host_",
   srcs: [
+    "src/base/event_fd.cc",
+    "src/base/file_utils.cc",
+    "src/base/metatrace.cc",
+    "src/base/paged_memory.cc",
+    "src/base/pipe.cc",
+    "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
+    "src/base/string_view.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/uuid.cc",
+    "src/base/virtual_destructors.cc",
+    "src/base/waitable_event.cc",
+    "src/base/watchdog_posix.cc",
     "src/protozero/protoc_plugin/protozero_plugin.cc",
   ],
   shared_libs: [
@@ -3959,206 +4090,6 @@
   ],
 }
 
-// GN target: //src/tracing:ipc
-cc_library_static {
-  name: "perfetto_src_tracing_ipc",
-  srcs: [
-    ":perfetto_protos_perfetto_common_lite_gen",
-    ":perfetto_protos_perfetto_common_zero_gen",
-    ":perfetto_protos_perfetto_config_android_lite_gen",
-    ":perfetto_protos_perfetto_config_android_zero_gen",
-    ":perfetto_protos_perfetto_config_ftrace_lite_gen",
-    ":perfetto_protos_perfetto_config_ftrace_zero_gen",
-    ":perfetto_protos_perfetto_config_gpu_lite_gen",
-    ":perfetto_protos_perfetto_config_gpu_zero_gen",
-    ":perfetto_protos_perfetto_config_inode_file_lite_gen",
-    ":perfetto_protos_perfetto_config_inode_file_zero_gen",
-    ":perfetto_protos_perfetto_config_lite_gen",
-    ":perfetto_protos_perfetto_config_power_lite_gen",
-    ":perfetto_protos_perfetto_config_power_zero_gen",
-    ":perfetto_protos_perfetto_config_process_stats_lite_gen",
-    ":perfetto_protos_perfetto_config_process_stats_zero_gen",
-    ":perfetto_protos_perfetto_config_profiling_lite_gen",
-    ":perfetto_protos_perfetto_config_profiling_zero_gen",
-    ":perfetto_protos_perfetto_config_sys_stats_lite_gen",
-    ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
-    ":perfetto_protos_perfetto_config_zero_gen",
-    ":perfetto_protos_perfetto_ipc_ipc_gen",
-    ":perfetto_protos_perfetto_trace_android_zero_gen",
-    ":perfetto_protos_perfetto_trace_chrome_zero_gen",
-    ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
-    ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
-    ":perfetto_protos_perfetto_trace_gpu_zero_gen",
-    ":perfetto_protos_perfetto_trace_interned_data_zero_gen",
-    ":perfetto_protos_perfetto_trace_minimal_lite_gen",
-    ":perfetto_protos_perfetto_trace_minimal_zero_gen",
-    ":perfetto_protos_perfetto_trace_non_minimal_zero_gen",
-    ":perfetto_protos_perfetto_trace_perfetto_zero_gen",
-    ":perfetto_protos_perfetto_trace_power_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_track_event_zero_gen",
-    ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
-    "src/base/event_fd.cc",
-    "src/base/file_utils.cc",
-    "src/base/metatrace.cc",
-    "src/base/paged_memory.cc",
-    "src/base/pipe.cc",
-    "src/base/string_splitter.cc",
-    "src/base/string_utils.cc",
-    "src/base/string_view.cc",
-    "src/base/temp_file.cc",
-    "src/base/thread_checker.cc",
-    "src/base/thread_task_runner.cc",
-    "src/base/time.cc",
-    "src/base/unix_socket.cc",
-    "src/base/unix_task_runner.cc",
-    "src/base/uuid.cc",
-    "src/base/virtual_destructors.cc",
-    "src/base/waitable_event.cc",
-    "src/base/watchdog_posix.cc",
-    "src/ipc/buffered_frame_deserializer.cc",
-    "src/ipc/client_impl.cc",
-    "src/ipc/deferred.cc",
-    "src/ipc/host_impl.cc",
-    "src/ipc/service_proxy.cc",
-    "src/ipc/virtual_destructors.cc",
-    "src/protozero/message.cc",
-    "src/protozero/message_handle.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/tracing/core/chrome_config.cc",
-    "src/tracing/core/commit_data_request.cc",
-    "src/tracing/core/data_source_config.cc",
-    "src/tracing/core/data_source_descriptor.cc",
-    "src/tracing/core/id_allocator.cc",
-    "src/tracing/core/metatrace_writer.cc",
-    "src/tracing/core/null_trace_writer.cc",
-    "src/tracing/core/observable_events.cc",
-    "src/tracing/core/packet_stream_validator.cc",
-    "src/tracing/core/shared_memory_abi.cc",
-    "src/tracing/core/shared_memory_arbiter_impl.cc",
-    "src/tracing/core/sliced_protobuf_input_stream.cc",
-    "src/tracing/core/startup_trace_writer.cc",
-    "src/tracing/core/startup_trace_writer_registry.cc",
-    "src/tracing/core/test_config.cc",
-    "src/tracing/core/trace_buffer.cc",
-    "src/tracing/core/trace_config.cc",
-    "src/tracing/core/trace_packet.cc",
-    "src/tracing/core/trace_stats.cc",
-    "src/tracing/core/trace_writer_impl.cc",
-    "src/tracing/core/tracing_service_impl.cc",
-    "src/tracing/core/tracing_service_state.cc",
-    "src/tracing/core/virtual_destructors.cc",
-    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
-    "src/tracing/ipc/default_socket.cc",
-    "src/tracing/ipc/posix_shared_memory.cc",
-    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
-    "src/tracing/ipc/service/consumer_ipc_service.cc",
-    "src/tracing/ipc/service/producer_ipc_service.cc",
-    "src/tracing/ipc/service/service_ipc_host_impl.cc",
-    "src/tracing/trace_writer_base.cc",
-  ],
-  shared_libs: [
-    "libprotobuf-cpp-lite",
-  ],
-  export_include_dirs: [
-    "include",
-    "include/perfetto/base/build_configs/android_tree",
-  ],
-  generated_headers: [
-    "perfetto_protos_perfetto_common_lite_gen_headers",
-    "perfetto_protos_perfetto_common_zero_gen_headers",
-    "perfetto_protos_perfetto_config_android_lite_gen_headers",
-    "perfetto_protos_perfetto_config_android_zero_gen_headers",
-    "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
-    "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
-    "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
-    "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
-    "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
-    "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
-    "perfetto_protos_perfetto_config_lite_gen_headers",
-    "perfetto_protos_perfetto_config_power_lite_gen_headers",
-    "perfetto_protos_perfetto_config_power_zero_gen_headers",
-    "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
-    "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
-    "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
-    "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
-    "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
-    "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
-    "perfetto_protos_perfetto_config_zero_gen_headers",
-    "perfetto_protos_perfetto_ipc_ipc_gen_headers",
-    "perfetto_protos_perfetto_trace_android_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
-    "perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_power_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_track_event_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
-  ],
-  export_generated_headers: [
-    "perfetto_protos_perfetto_common_lite_gen_headers",
-    "perfetto_protos_perfetto_common_zero_gen_headers",
-    "perfetto_protos_perfetto_config_android_lite_gen_headers",
-    "perfetto_protos_perfetto_config_android_zero_gen_headers",
-    "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
-    "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
-    "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
-    "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
-    "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
-    "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
-    "perfetto_protos_perfetto_config_lite_gen_headers",
-    "perfetto_protos_perfetto_config_power_lite_gen_headers",
-    "perfetto_protos_perfetto_config_power_zero_gen_headers",
-    "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
-    "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
-    "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
-    "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
-    "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
-    "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
-    "perfetto_protos_perfetto_config_zero_gen_headers",
-    "perfetto_protos_perfetto_ipc_ipc_gen_headers",
-    "perfetto_protos_perfetto_trace_android_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
-    "perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_power_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_track_event_zero_gen_headers",
-    "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
-  ],
-  defaults: [
-    "perfetto_defaults",
-  ],
-  cflags: [
-    "-DGOOGLE_PROTOBUF_NO_RTTI",
-    "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-  ],
-}
-
 // GN target: //protos/perfetto/trace:perfetto_trace_protos
 cc_library_static {
   name: "perfetto_trace_protos",
@@ -4174,6 +4105,7 @@
     ":perfetto_protos_perfetto_config_profiling_lite_gen",
     ":perfetto_protos_perfetto_config_sys_stats_lite_gen",
     ":perfetto_protos_perfetto_trace_android_lite_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
     ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
@@ -4208,6 +4140,7 @@
     "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
@@ -4234,6 +4167,7 @@
     "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
@@ -4282,10 +4216,13 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_metrics_android_zero_gen",
     ":perfetto_protos_perfetto_metrics_zero_gen",
     ":perfetto_protos_perfetto_trace_android_lite_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_lite_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
@@ -4315,7 +4252,6 @@
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
     ":perfetto_src_ipc_test_messages_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     ":perfetto_src_perfetto_cmd_protos_gen",
     ":perfetto_src_protozero_testing_messages_lite_gen",
     ":perfetto_src_protozero_testing_messages_zero_gen",
@@ -4514,7 +4450,6 @@
     "src/trace_processor/trace_sorter.cc",
     "src/trace_processor/trace_sorter_unittest.cc",
     "src/trace_processor/trace_storage.cc",
-    "src/trace_processor/track_table.cc",
     "src/trace_processor/virtual_destructors.cc",
     "src/trace_processor/virtual_track_tracker.cc",
     "src/trace_processor/window_operator_table.cc",
@@ -4607,7 +4542,14 @@
     "src/tracing/core/tracing_service_impl_unittest.cc",
     "src/tracing/core/tracing_service_state.cc",
     "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
     "src/tracing/ipc/posix_shared_memory_unittest.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/test/aligned_buffer_test.cc",
     "src/tracing/test/fake_packet.cc",
     "src/tracing/test/mock_consumer.cc",
@@ -4632,7 +4574,6 @@
   ],
   static_libs: [
     "libgmock",
-    "perfetto_src_tracing_ipc",
   ],
   generated_headers: [
     "gen_merged_sql_metrics",
@@ -4657,10 +4598,13 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_metrics_android_zero_gen_headers",
     "perfetto_protos_perfetto_metrics_zero_gen_headers",
     "perfetto_protos_perfetto_trace_android_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
@@ -4690,7 +4634,6 @@
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
     "perfetto_src_ipc_test_messages_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
     "perfetto_src_protozero_testing_messages_lite_gen_headers",
     "perfetto_src_protozero_testing_messages_zero_gen_headers",
@@ -4723,7 +4666,7 @@
 }
 
 // GN target: //src/trace_processor:trace_processor_shell
-cc_binary {
+cc_binary_host {
   name: "trace_processor_shell",
   srcs: [
     ":perfetto_protos_perfetto_common_zero_gen",
@@ -4739,6 +4682,7 @@
     ":perfetto_protos_perfetto_metrics_android_zero_gen",
     ":perfetto_protos_perfetto_metrics_zero_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -4839,17 +4783,18 @@
     "src/trace_processor/trace_processor_shell.cc",
     "src/trace_processor/trace_sorter.cc",
     "src/trace_processor/trace_storage.cc",
-    "src/trace_processor/track_table.cc",
     "src/trace_processor/virtual_destructors.cc",
     "src/trace_processor/virtual_track_tracker.cc",
     "src/trace_processor/window_operator_table.cc",
   ],
   shared_libs: [
     "liblog",
-    "libprotobuf-cpp-full",
+    "libprotoc",
     "libz",
   ],
-  host_supported: true,
+  static_libs: [
+    "libsqlite",
+  ],
   generated_headers: [
     "gen_merged_sql_metrics",
     "perfetto_protos_perfetto_common_zero_gen_headers",
@@ -4865,6 +4810,7 @@
     "perfetto_protos_perfetto_metrics_android_zero_gen_headers",
     "perfetto_protos_perfetto_metrics_zero_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -4888,21 +4834,6 @@
     "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
     "-DHAVE_HIDDEN",
   ],
-  target: {
-    android: {
-      shared_libs: [
-        "libandroidicu",
-        "liblog",
-        "libsqlite",
-        "libutils",
-      ],
-    },
-    host: {
-      static_libs: [
-        "libsqlite",
-      ],
-    },
-  },
 }
 
 // GN target: //tools/trace_to_text:trace_to_text
@@ -4933,6 +4864,8 @@
     ":perfetto_protos_perfetto_metrics_zero_gen",
     ":perfetto_protos_perfetto_trace_android_lite_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_lite_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
@@ -5045,7 +4978,6 @@
     "src/trace_processor/trace_processor_impl.cc",
     "src/trace_processor/trace_sorter.cc",
     "src/trace_processor/trace_storage.cc",
-    "src/trace_processor/track_table.cc",
     "src/trace_processor/virtual_destructors.cc",
     "src/trace_processor/virtual_track_tracker.cc",
     "src/trace_processor/window_operator_table.cc",
@@ -5097,6 +5029,8 @@
     "perfetto_protos_perfetto_metrics_zero_gen_headers",
     "perfetto_protos_perfetto_trace_android_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
@@ -5198,7 +5132,9 @@
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_ipc_wire_protocol_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_appended_data_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
@@ -5214,7 +5150,6 @@
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_protos_perfetto_trace_trusted_lite_gen",
-    ":perfetto_src_ipc_wire_protocol_gen",
     ":perfetto_src_perfetto_cmd_protos_gen",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
@@ -5272,15 +5207,19 @@
     "src/tracing/core/tracing_service_impl.cc",
     "src/tracing/core/tracing_service_state.cc",
     "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/ipc/consumer/consumer_ipc_client_impl.cc",
+    "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/posix_shared_memory.cc",
+    "src/tracing/ipc/producer/producer_ipc_client_impl.cc",
+    "src/tracing/ipc/service/consumer_ipc_service.cc",
+    "src/tracing/ipc/service/producer_ipc_service.cc",
+    "src/tracing/ipc/service/service_ipc_host_impl.cc",
     "src/tracing/trace_writer_base.cc",
   ],
   shared_libs: [
     "liblog",
     "libprotobuf-cpp-lite",
   ],
-  static_libs: [
-    "perfetto_src_tracing_ipc",
-  ],
   generated_headers: [
     "perfetto_protos_perfetto_common_lite_gen_headers",
     "perfetto_protos_perfetto_common_zero_gen_headers",
@@ -5303,7 +5242,9 @@
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_ipc_wire_protocol_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_appended_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
@@ -5319,7 +5260,6 @@
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
-    "perfetto_src_ipc_wire_protocol_gen_headers",
     "perfetto_src_perfetto_cmd_protos_gen_headers",
   ],
   defaults: [
@@ -5332,6 +5272,7 @@
 }
 
 // These targets are appended to the autogenerated Android.bp by tools/gen_android_bp.
+
 cc_library_static {
   name: "perfetto_cts_deps",
   srcs: [
@@ -5355,7 +5296,7 @@
   static_libs: [
     "libgmock",
     "libgtest",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
     "perfetto_trace_protos",
   ],
   defaults: [
@@ -5375,7 +5316,7 @@
   ],
   static_libs: [
     "libgtest",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
   ],
   defaults: [
     "perfetto_defaults",
@@ -5401,7 +5342,6 @@
   ],
   static_libs: [
     "libperfetto_client_experimental",
-    "perfetto_src_tracing_ipc",
     "perfetto_trace_protos",
   ],
   shared_libs: [
@@ -5415,4 +5355,18 @@
   defaults: [
     "perfetto_defaults",
   ],
+}
+
+// TODO(primiano): remove this target after the dep from
+// frameworks/native/services/surfaceflinger/Android.bp goes away. This has been
+// introduced only to avoid multi-repo atomic CLs.
+
+cc_library_static {
+  name: "perfetto_src_tracing_ipc",
+  srcs: [
+    "src/android_internal/empty_file.cc",
+  ],
+  defaults: [
+    "perfetto_defaults",
+  ],
 }
\ No newline at end of file
diff --git a/Android.bp.extras b/Android.bp.extras
index 354c97a..8aff22b 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -1,4 +1,5 @@
 // These targets are appended to the autogenerated Android.bp by tools/gen_android_bp.
+
 cc_library_static {
   name: "perfetto_cts_deps",
   srcs: [
@@ -22,7 +23,7 @@
   static_libs: [
     "libgmock",
     "libgtest",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
     "perfetto_trace_protos",
   ],
   defaults: [
@@ -42,7 +43,7 @@
   ],
   static_libs: [
     "libgtest",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
   ],
   defaults: [
     "perfetto_defaults",
@@ -68,7 +69,6 @@
   ],
   static_libs: [
     "libperfetto_client_experimental",
-    "perfetto_src_tracing_ipc",
     "perfetto_trace_protos",
   ],
   shared_libs: [
@@ -83,3 +83,17 @@
     "perfetto_defaults",
   ],
 }
+
+// TODO(primiano): remove this target after the dep from
+// frameworks/native/services/surfaceflinger/Android.bp goes away. This has been
+// introduced only to avoid multi-repo atomic CLs.
+
+cc_library_static {
+  name: "perfetto_src_tracing_ipc",
+  srcs: [
+    "src/android_internal/empty_file.cc",
+  ],
+  defaults: [
+    "perfetto_defaults",
+  ],
+}
diff --git a/BUILD b/BUILD
index 2fec784..5177498 100644
--- a/BUILD
+++ b/BUILD
@@ -171,6 +171,8 @@
         "//third_party/perfetto/protos:protos_third_party_pprof_cc_proto",
         "//third_party/perfetto/protos:trace_android_cc_proto",
         "//third_party/perfetto/protos:trace_android_zero_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_zero_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_zero_cc_proto",
         "//third_party/perfetto/protos:trace_filesystem_cc_proto",
@@ -270,6 +272,64 @@
 cc_binary(
     name = "src_protozero_protoc_plugin_protozero_plugin",
     srcs = [
+        "include/perfetto/base/build_config.h",
+        "include/perfetto/base/build_configs/bazel/perfetto_build_flags.h",
+        "include/perfetto/base/compiler.h",
+        "include/perfetto/base/copyable_ptr.h",
+        "include/perfetto/base/export.h",
+        "include/perfetto/base/logging.h",
+        "include/perfetto/base/task_runner.h",
+        "include/perfetto/base/time.h",
+        "include/perfetto/ext/base/circular_queue.h",
+        "include/perfetto/ext/base/container_annotations.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",
+        "include/perfetto/ext/base/optional.h",
+        "include/perfetto/ext/base/paged_memory.h",
+        "include/perfetto/ext/base/pipe.h",
+        "include/perfetto/ext/base/proc_utils.h",
+        "include/perfetto/ext/base/scoped_file.h",
+        "include/perfetto/ext/base/small_set.h",
+        "include/perfetto/ext/base/string_splitter.h",
+        "include/perfetto/ext/base/string_utils.h",
+        "include/perfetto/ext/base/string_view.h",
+        "include/perfetto/ext/base/string_writer.h",
+        "include/perfetto/ext/base/temp_file.h",
+        "include/perfetto/ext/base/thread_annotations.h",
+        "include/perfetto/ext/base/thread_checker.h",
+        "include/perfetto/ext/base/thread_task_runner.h",
+        "include/perfetto/ext/base/thread_utils.h",
+        "include/perfetto/ext/base/unix_socket.h",
+        "include/perfetto/ext/base/unix_task_runner.h",
+        "include/perfetto/ext/base/utils.h",
+        "include/perfetto/ext/base/uuid.h",
+        "include/perfetto/ext/base/waitable_event.h",
+        "include/perfetto/ext/base/watchdog.h",
+        "include/perfetto/ext/base/watchdog_noop.h",
+        "include/perfetto/ext/base/watchdog_posix.h",
+        "include/perfetto/ext/base/weak_ptr.h",
+        "src/base/event_fd.cc",
+        "src/base/file_utils.cc",
+        "src/base/metatrace.cc",
+        "src/base/paged_memory.cc",
+        "src/base/pipe.cc",
+        "src/base/string_splitter.cc",
+        "src/base/string_utils.cc",
+        "src/base/string_view.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/uuid.cc",
+        "src/base/virtual_destructors.cc",
+        "src/base/waitable_event.cc",
+        "src/base/watchdog_posix.cc",
         "src/protozero/protoc_plugin/protozero_plugin.cc",
     ],
     deps = [
@@ -455,8 +515,6 @@
         "src/trace_processor/trace_sorter.h",
         "src/trace_processor/trace_storage.cc",
         "src/trace_processor/trace_storage.h",
-        "src/trace_processor/track_table.cc",
-        "src/trace_processor/track_table.h",
         "src/trace_processor/variadic.h",
         "src/trace_processor/virtual_destructors.cc",
         "src/trace_processor/virtual_track_tracker.cc",
@@ -537,6 +595,7 @@
         "//third_party/perfetto/protos:metrics_android_zero_cc_proto",
         "//third_party/perfetto/protos:metrics_zero_cc_proto",
         "//third_party/perfetto/protos:trace_android_zero_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_zero_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_zero_cc_proto",
         "//third_party/perfetto/protos:trace_filesystem_zero_cc_proto",
         "//third_party/perfetto/protos:trace_ftrace_zero_cc_proto",
@@ -562,6 +621,7 @@
     name = "trace_processor_shell",
     srcs = [
         "include/perfetto/base/build_config.h",
+        "include/perfetto/base/build_configs/bazel/perfetto_build_flags.h",
         "include/perfetto/base/compiler.h",
         "include/perfetto/base/copyable_ptr.h",
         "include/perfetto/base/export.h",
@@ -791,8 +851,6 @@
         "src/trace_processor/trace_sorter.h",
         "src/trace_processor/trace_storage.cc",
         "src/trace_processor/trace_storage.h",
-        "src/trace_processor/track_table.cc",
-        "src/trace_processor/track_table.h",
         "src/trace_processor/variadic.h",
         "src/trace_processor/virtual_destructors.cc",
         "src/trace_processor/virtual_track_tracker.cc",
@@ -817,6 +875,7 @@
         "//third_party/perfetto/protos:metrics_android_zero_cc_proto",
         "//third_party/perfetto/protos:metrics_zero_cc_proto",
         "//third_party/perfetto/protos:trace_android_zero_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_zero_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_zero_cc_proto",
         "//third_party/perfetto/protos:trace_filesystem_zero_cc_proto",
         "//third_party/perfetto/protos:trace_ftrace_zero_cc_proto",
@@ -844,6 +903,7 @@
     name = "trace_to_text",
     srcs = [
         "include/perfetto/base/build_config.h",
+        "include/perfetto/base/build_configs/bazel/perfetto_build_flags.h",
         "include/perfetto/base/compiler.h",
         "include/perfetto/base/copyable_ptr.h",
         "include/perfetto/base/export.h",
@@ -1070,8 +1130,6 @@
         "src/trace_processor/trace_sorter.h",
         "src/trace_processor/trace_storage.cc",
         "src/trace_processor/trace_storage.h",
-        "src/trace_processor/track_table.cc",
-        "src/trace_processor/track_table.h",
         "src/trace_processor/variadic.h",
         "src/trace_processor/virtual_destructors.cc",
         "src/trace_processor/virtual_track_tracker.cc",
@@ -1130,6 +1188,8 @@
         "//third_party/perfetto/protos:protos_third_party_pprof_cc_proto",
         "//third_party/perfetto/protos:trace_android_cc_proto",
         "//third_party/perfetto/protos:trace_android_zero_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_cc_proto",
+        "//third_party/perfetto/protos:trace_appended_data_zero_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_cc_proto",
         "//third_party/perfetto/protos:trace_chrome_zero_cc_proto",
         "//third_party/perfetto/protos:trace_filesystem_cc_proto",
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index db40ea5..b491a6e 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -23,7 +23,7 @@
       x, white_list=".*",
       black_list=['Android[.]bp', '.*[.]json$', '.*[.]sql$', '.*[.]out$',
                   'test/trace_processor/index$', 'BUILD$', 'protos/BUILD$',
-                  '.*/Makefile$'])
+                  '.*/Makefile$', '/perfetto_build_flags.h$'])
   results = []
   results += input.canned_checks.CheckDoNotSubmit(input, output)
   results += input.canned_checks.CheckChangeHasNoTabs(input, output)
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index cdbdb6a..220fb8b 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -273,111 +273,111 @@
   public_configs = [ ":protobuf_gen_config" ]
 }
 
-if (current_toolchain == host_toolchain) {
-  source_set("protoc_lib") {
-    visibility = _buildtools_visibility
-    deps = [
-      ":protobuf_full",
-      "//gn:default_deps",
-    ]
-    sources = [
-      "protobuf/src/google/protobuf/compiler/code_generator.cc",
-      "protobuf/src/google/protobuf/compiler/command_line_interface.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc",
-      "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_field_base.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_generator.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_helpers.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_map_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_message.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_reflection_class.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc",
-      "protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_context.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_enum.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_extension.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_file.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_generator.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_helpers.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_lazy_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_lazy_message_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_map_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message_builder.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_message_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_service.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_string_field.cc",
-      "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_enum.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_extension.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_field.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_file.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_generator.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_helpers.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_map_field.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_message.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/js/js_generator.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc",
-      "protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc",
-      "protobuf/src/google/protobuf/compiler/plugin.cc",
-      "protobuf/src/google/protobuf/compiler/plugin.pb.cc",
-      "protobuf/src/google/protobuf/compiler/python/python_generator.cc",
-      "protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc",
-      "protobuf/src/google/protobuf/compiler/subprocess.cc",
-      "protobuf/src/google/protobuf/compiler/zip_writer.cc",
-    ]
-    configs -= [ "//gn/standalone:extra_warnings" ]
-    configs += [ ":protobuf_config" ]
-    public_configs = [ ":protobuf_gen_config" ]
-  }
+source_set("protoc_lib") {
+  visibility = _buildtools_visibility
+  deps = [
+    ":protobuf_full",
+    "//gn:default_deps",
+  ]
+  sources = [
+    "protobuf/src/google/protobuf/compiler/code_generator.cc",
+    "protobuf/src/google/protobuf/compiler/command_line_interface.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc",
+    "protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_field_base.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_generator.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_helpers.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_map_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_message.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_reflection_class.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc",
+    "protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_context.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_enum.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_extension.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_file.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_generator.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_helpers.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_lazy_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_lazy_message_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_map_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message_builder.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_message_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_service.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_string_field.cc",
+    "protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_enum.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_extension.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_field.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_file.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_generator.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_helpers.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_map_field.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_message.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/js/js_generator.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc",
+    "protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc",
+    "protobuf/src/google/protobuf/compiler/plugin.cc",
+    "protobuf/src/google/protobuf/compiler/plugin.pb.cc",
+    "protobuf/src/google/protobuf/compiler/python/python_generator.cc",
+    "protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc",
+    "protobuf/src/google/protobuf/compiler/subprocess.cc",
+    "protobuf/src/google/protobuf/compiler/zip_writer.cc",
+  ]
+  configs -= [ "//gn/standalone:extra_warnings" ]
+  configs += [ ":protobuf_config" ]
+  public_configs = [ ":protobuf_gen_config" ]
+}
 
+if (current_toolchain == host_toolchain) {
   executable("protoc") {
     visibility = _buildtools_visibility
     deps = [
diff --git a/docs/embedder-guide.md b/docs/embedder-guide.md
index 226e086..fbe3be3 100644
--- a/docs/embedder-guide.md
+++ b/docs/embedder-guide.md
@@ -20,7 +20,7 @@
   - [service.h](/include/perfetto/tracing/core/tracing_service.h)
 - Explain the ABI surface:
   - [shared_memory_abi.h](/include/perfetto/tracing/core/shared_memory_abi.h)
-  - IPC's [wire protocol](/src/ipc/wire_protocol.proto) (if used)
+  - IPC's [wire protocol](/protos/perfetto/ipc/wire_protocol.proto) (if used)
   - The input [config protos](/protos/perfetto/config)
   - The output [trace protos](/protos/perfetto/trace)
 
diff --git a/docs/heapprofd.md b/docs/heapprofd.md
index 2a330cd..96a259c 100644
--- a/docs/heapprofd.md
+++ b/docs/heapprofd.md
@@ -230,6 +230,7 @@
 
 ## Known Issues
 
+### Android 10
 * Does not work on x86 platforms (including the Android cuttlefish emulator).
 * If heapprofd is run standalone (by running `heapprofd` in a root shell, rather
   than through init), `/dev/socket/heapprofd` get assigned an incorrect SELinux
diff --git a/docs/ipc.md b/docs/ipc.md
index 33d3a5c..ac7c36a 100644
--- a/docs/ipc.md
+++ b/docs/ipc.md
@@ -18,7 +18,7 @@
 - Allows to send file descriptors over the wire: for setting up shared memory
   and passing the FD for the output trace from a consumer to the service.
 - Service definition uses same protobuf rpc syntax of [gRPC](https://grpc.io)
-- Extremely simple [wire protocol](/src/ipc/wire_protocol.proto).
+- Extremely simple [wire protocol](/protos/perfetto/ipc/wire_protocol.proto).
 - C++11 friendly, allows to bind `std::function` to each request.
 - Leak (un)friendly: tries hard to guarantee that callbacks are left unresolved,
   using C++11 move semantics.
@@ -31,7 +31,7 @@
 - Debugging friendly: single-thread only, based on non-blocking socket I/O.
 - Binary size friendly: generates one protobuf per message, doesn't have any
   external dependency.
-- Hopefully safe:
+- Safe:
   - The rx buffer has guard regions around.
   - The wire protocol is based on protobuf.
   - [Fuzzed](/src/ipc/buffered_frame_deserializer_fuzzer.cc)
diff --git a/docs/running.md b/docs/running.md
index 13b13ff..7529186 100644
--- a/docs/running.md
+++ b/docs/running.md
@@ -3,17 +3,17 @@
 In order to run Perfetto and get a meaningful trace you need to build
 (see [build instructions](build-instructions.md)) and run the following:
 
-`traced`:  
+`traced`:
 The unprivileged trace daemon that owns the log buffers and maintains
 a registry of Producers and Consumers connected.
 
-`traced_probes`:  
+`traced_probes`:
 The privileged daemon that has access to the Kernel tracefs
 (typically mounted under `/sys/kernel/debug/tracing`). It drives
 [Ftrace](https://source.android.com/devices/tech/debug/ftrace) and writes its
 protobuf-translated contents into `traced`.
 
-`perfetto`:  
+`perfetto`:
 A command line utility client that drive the trace and save back
 the results (either to a file or to [Android's Dropbox][dropbox])
 
@@ -29,10 +29,10 @@
 `CONFIG` variable (e.g., [this](https://android.googlesource.com/platform/external/perfetto/+/master/test/configs/ftrace.cfg)) into a protobuf and setup the right paths.
 Furthermore it will automatically rebuild if necessary.
 
-When doing a Linux cross-build it is possible to specify a target to run the
-daemons on via SSH:
+It is possible to push binaries to, and run on, a remote target over ssh (even
+when cross-compiling):
 ```bash
-CONFIG=ftrace.cfg OUT=out/default TARGET=user@my-device-host ./tools/tmux
+CONFIG=ftrace.cfg OUT=out/default SSH_TARGET=user@my-device-host ./tools/tmux
 ```
 
 Running from an Android P+ in-tree build
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 6e2afb4..a8aecc1 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -43,6 +43,15 @@
   perfetto_force_dlog_on = perfetto_force_dlog == "on"
   perfetto_force_dlog_off = perfetto_force_dlog == "off"
 
+  # We can't just use (is_linux || is_android) in perfetto.gni because that
+  # doesn't work in Android Mac host builds. We lose the GN notion of OS once
+  # we run the tools/gen_xxx generators.
+  if (enable_perfetto_watchdog) {
+    perfetto_watchdog = "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || " +
+                        "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX()"
+  } else {
+    perfetto_watchdog = "0"
+  }
   response_file_contents = [
     "--flags",  # Keep this marker first.
     "PERFETTO_ANDROID_BUILD=$perfetto_build_with_android",
@@ -50,7 +59,7 @@
     "PERFETTO_STANDALONE_BUILD=$perfetto_build_standalone",
     "PERFETTO_START_DAEMONS=$start_daemons_for_testing",
     "PERFETTO_IPC=$enable_perfetto_ipc",
-    "PERFETTO_WATCHDOG=$enable_perfetto_watchdog",
+    "PERFETTO_WATCHDOG=$perfetto_watchdog",
     "PERFETTO_COMPONENT_BUILD=$perfetto_component_build",
     "PERFETTO_FORCE_DLOG_ON=$perfetto_force_dlog_on",
     "PERFETTO_FORCE_DLOG_OFF=$perfetto_force_dlog_off",
@@ -164,26 +173,39 @@
   }
 }
 
-# protoc compiler library, for building protoc plugins on the host.
-if (current_toolchain == host_toolchain) {
-  group("protoc_lib") {
+# Full protobuf is just for host tools .No binary shipped on device should
+# depend on this.
+whitelisted_protobuf_full_deps = [
+  "../tools/*",
+  "../src/ipc/protoc_plugin:*",
+  "../src/protozero/protoc_plugin:*",
+  "../src/trace_processor:trace_processor_shell",
+]
+
+group("protoc") {
+  public_deps = [
+    "${perfetto_protobuf_target_prefix}:protoc($host_toolchain)",
+  ]
+}
+
+# protoc compiler library, it's used for building protoc plugins and by
+# trace_processor_shell to dynamically load .proto files for metrics.
+group("protoc_lib") {
+  visibility = whitelisted_protobuf_full_deps
+  if (current_toolchain == host_toolchain) {
     public_deps = [
       "${perfetto_protobuf_target_prefix}:protoc_lib",
     ]
   }
-
-  group("protoc") {
-    public_deps = [
-      "${perfetto_protobuf_target_prefix}:protoc",
-    ]
-  }
 }
 
 group("protobuf_full") {
-  testonly = true
-  public_deps = [
-    "${perfetto_protobuf_target_prefix}:protobuf_full",
-  ]
+  visibility = whitelisted_protobuf_full_deps
+  if (current_toolchain == host_toolchain) {
+    public_deps = [
+      "${perfetto_protobuf_target_prefix}:protobuf_full",
+    ]
+  }
 }
 
 group("protobuf_lite") {
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 0653d07..4bf6252 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -154,13 +154,15 @@
       is_perfetto_build_generator
 
   # Enables base::Watchdog. Is supported only on Linux-based platforms.
+  # gn/BUILD.gn further restricts this to OS_LINUX || OS_ANDROID when generating
+  # the perfetto_build_flags.h header.
   enable_perfetto_watchdog =
-      perfetto_build_with_android ||
-      (perfetto_build_standalone && (is_linux || is_android) && !is_wasm)
+      perfetto_build_with_android || perfetto_build_standalone
 
   # Misc host executable under tools/.
   enable_perfetto_tools =
-      perfetto_build_standalone || perfetto_build_with_android
+      (perfetto_build_standalone && current_toolchain == host_toolchain) ||
+      perfetto_build_with_android
 
   # Allows to build the UI (TypeScript/ HTML / WASM)
   enable_perfetto_ui = perfetto_build_standalone
diff --git a/gn/perfetto_benchmarks.gni b/gn/perfetto_benchmarks.gni
index 83f780f..8b6d87e 100644
--- a/gn/perfetto_benchmarks.gni
+++ b/gn/perfetto_benchmarks.gni
@@ -17,6 +17,7 @@
 perfetto_benchmarks_targets = [
   "gn:default_deps",
   "src/traced/probes/ftrace:benchmarks",
+  "src/tracing:benchmarks",
   "test:benchmark_main",
   "test:end_to_end_benchmarks",
 ]
diff --git a/gn/perfetto_host_executable.gni b/gn/perfetto_host_executable.gni
index 15e9d4a..2f00737 100644
--- a/gn/perfetto_host_executable.gni
+++ b/gn/perfetto_host_executable.gni
@@ -35,22 +35,32 @@
       }
     } else {
       not_needed(invoker, "*", [ "testonly" ])
-      copy(target_name) {
-        if (defined(invoker.testonly)) {
-          testonly = invoker.testonly
+      _host_target = ":$target_name($host_toolchain)"
+      _testonly = defined(invoker.testonly) && invoker.testonly
+      if (perfetto_build_with_embedder) {
+        # Don't copy anythin in Chromium, just add a dependency to the host
+        # target. V8 and other GN embedder builds. This causes problems on
+        # some bot (see crbug.com/1002599).
+        group(target_name) {
+          testonly = _testonly
+          deps = [
+            _host_target,
+          ]
         }
-        host_target = ":$target_name($host_toolchain)"
-        deps = [
-          host_target,
-        ]
-
-        host_out_dir = get_label_info(host_target, "root_out_dir")
-        sources = [
-          "$host_out_dir/$target_name",
-        ]
-        outputs = [
-          "$root_out_dir/$target_name",
-        ]
+      } else {
+        copy(target_name) {
+          testonly = _testonly
+          deps = [
+            _host_target,
+          ]
+          _host_out_dir = get_label_info(_host_target, "root_out_dir")
+          sources = [
+            "$_host_out_dir/$target_name",
+          ]
+          outputs = [
+            "$root_out_dir/$target_name",
+          ]
+        }
       }
     }
   }  # if (is_perfetto_build_generator)
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index f586a24..5d211de 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -76,9 +76,12 @@
     "-fPIC",
     "-g",
     "-Wformat",
-    "-Werror",
   ]
 
+  if (!is_fuzzer) {
+    cflags += [ "-Werror" ]
+  }
+
   if (is_clang) {
     cflags += [
       # Color compiler output, see https://github.com/ninja-build/ninja/wiki/FAQ
@@ -105,7 +108,10 @@
       "-msse2",
       "-mfpmath=sse",
     ]
-    ldflags += [ "-m32" ]
+    ldflags += [
+      "-m32",
+      "-lgcc",
+    ]
   } else if (current_cpu == "arm64") {
     cflags += [ "-fno-omit-frame-pointer" ]
   }
diff --git a/gn/write_buildflag_header.py b/gn/write_buildflag_header.py
index 449d264..00c8c14 100644
--- a/gn/write_buildflag_header.py
+++ b/gn/write_buildflag_header.py
@@ -54,9 +54,11 @@
   lines.append('#ifndef %s' % guard)
   lines.append('#define %s' % guard)
   lines.append('')
+  lines.append('// clang-format off')
   for kv in flags:
     lines.append('#define PERFETTO_BUILDFLAG_DEFINE_%s() (%s)' % kv)
   lines.append('')
+  lines.append('// clang-format on')
   lines.append('#endif  // %s' % guard)
   lines.append('')
 
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index 0ed2cb2..bb641f5 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -17,18 +17,6 @@
 #ifndef INCLUDE_PERFETTO_BASE_BUILD_CONFIG_H_
 #define INCLUDE_PERFETTO_BASE_BUILD_CONFIG_H_
 
-// perfetto_build_flags.h contains the tweakable build flags defined via GN.
-// - In GN builds (e.g., standalone, chromium, v8) this file is generated at
-//   build time via the gen_rule //gn/gen_buildflags.
-// - In Android in-tree builds, this file is generated by tools/gen_android_bp
-//   and checked in into include/perfetto/base/build_configs/android_tree/. The
-//   default cflags add this path to the default include path.
-// - Similarly, in bazel builds, this file is generated by tools/gen_bazel and
-//   checked in into include/perfetto/base/build_configs/bazel/.
-// - In amaglamated builds, this file is generated by tools/gen_amalgamated and
-//   added to the amalgamated headers.
-#include "perfetto_build_flags.h"  // no-include-violation-check
-
 // Allows to define build flags that give a compiler error if the header that
 // defined the flag is not included, instead of silently ignoring the #if block.
 #define PERFETTO_BUILDFLAG_CAT_INDIRECT(a, b) a##b
@@ -106,4 +94,16 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ANDROID_USERDEBUG_BUILD() 0
 #endif
 
+// perfetto_build_flags.h contains the tweakable build flags defined via GN.
+// - In GN builds (e.g., standalone, chromium, v8) this file is generated at
+//   build time via the gen_rule //gn/gen_buildflags.
+// - In Android in-tree builds, this file is generated by tools/gen_android_bp
+//   and checked in into include/perfetto/base/build_configs/android_tree/. The
+//   default cflags add this path to the default include path.
+// - Similarly, in bazel builds, this file is generated by tools/gen_bazel and
+//   checked in into include/perfetto/base/build_configs/bazel/.
+// - In amaglamated builds, this file is generated by tools/gen_amalgamated and
+//   added to the amalgamated headers.
+#include "perfetto_build_flags.h"  // no-include-violation-check
+
 #endif  // INCLUDE_PERFETTO_BASE_BUILD_CONFIG_H_
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 bef52ba..f0594ab 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
@@ -4,12 +4,13 @@
 #ifndef GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
 #define GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
 
+// clang-format off
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ANDROID_BUILD() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_CHROMIUM_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_START_DAEMONS() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_IPC() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_WATCHDOG() (1)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_WATCHDOG() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX())
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPONENT_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_ON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_OFF() (0)
@@ -18,4 +19,5 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_PERCENTILE() (0)
 
+// clang-format on
 #endif  // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
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 d32a21c..92250a0 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -4,12 +4,13 @@
 #ifndef GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
 #define GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
 
+// clang-format off
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ANDROID_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_CHROMIUM_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_START_DAEMONS() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_IPC() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_WATCHDOG() (1)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_WATCHDOG() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX())
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_COMPONENT_BUILD() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_ON() (0)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DLOG_OFF() (0)
@@ -18,4 +19,5 @@
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (1)
 #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_PERCENTILE() (1)
 
+// clang-format on
 #endif  // GEN_BUILD_CONFIG_PERFETTO_BUILD_FLAGS_H_
diff --git a/include/perfetto/ext/base/string_utils.h b/include/perfetto/ext/base/string_utils.h
index 3220a69..977673f 100644
--- a/include/perfetto/ext/base/string_utils.h
+++ b/include/perfetto/ext/base/string_utils.h
@@ -23,6 +23,14 @@
 namespace perfetto {
 namespace base {
 
+inline char Lowercase(char c) {
+  return ('A' <= c && c <= 'Z') ? (c -= 'A' - 'a') : c;
+}
+
+inline char Uppercase(char c) {
+  return ('a' <= c && c <= 'z') ? (c += 'A' - 'a') : c;
+}
+
 bool StartsWith(const std::string& str, const std::string& prefix);
 bool EndsWith(const std::string& str, const std::string& suffix);
 bool Contains(const std::string& haystack, const std::string& needle);
@@ -31,6 +39,16 @@
                  const std::string& delim);
 std::vector<std::string> SplitString(const std::string& text,
                                      const std::string& delimiter);
+std::string StripPrefix(const std::string& str, const std::string& prefix);
+std::string StripSuffix(const std::string& str, const std::string& suffix);
+std::string ToUpper(const std::string& str);
+std::string StripChars(const std::string& str,
+                       const std::string& chars,
+                       char replacement);
+std::string ToHex(const char* data, size_t size);
+inline std::string ToHex(const std::string& s) {
+  return ToHex(s.c_str(), s.size());
+}
 
 }  // namespace base
 }  // namespace perfetto
diff --git a/include/perfetto/ext/ipc/BUILD.gn b/include/perfetto/ext/ipc/BUILD.gn
index a8aa17e..53a39ee 100644
--- a/include/perfetto/ext/ipc/BUILD.gn
+++ b/include/perfetto/ext/ipc/BUILD.gn
@@ -14,6 +14,7 @@
 
 source_set("ipc") {
   public_deps = [
+    "../../../../gn:protobuf_lite",
     "../base",
   ]
   sources = [
diff --git a/include/perfetto/ext/ipc/async_result.h b/include/perfetto/ext/ipc/async_result.h
index cb4d849..5cb6140 100644
--- a/include/perfetto/ext/ipc/async_result.h
+++ b/include/perfetto/ext/ipc/async_result.h
@@ -28,8 +28,8 @@
 
 // Wraps the result of an asynchronous invocation. This is the equivalent of a
 // std::pair<unique_ptr<T>, bool> with syntactic sugar. It is used as callback
-// argument by Deferred<T>.
-template <typename T = ProtoMessage>
+// argument by Deferred<T>. T is a ProtoMessage subclass (i.e. generated .pb.h).
+template <typename T>
 class AsyncResult {
  public:
   static AsyncResult Create() {
diff --git a/include/perfetto/ext/ipc/basic_types.h b/include/perfetto/ext/ipc/basic_types.h
index 793cc19..f9e330c 100644
--- a/include/perfetto/ext/ipc/basic_types.h
+++ b/include/perfetto/ext/ipc/basic_types.h
@@ -21,11 +21,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
-namespace google {
-namespace protobuf {
-class MessageLite;
-}  // namespace protobuf
-}  // namespace google
+#include <google/protobuf/message_lite.h>
 
 namespace perfetto {
 namespace ipc {
diff --git a/include/perfetto/ext/ipc/codegen_helpers.h b/include/perfetto/ext/ipc/codegen_helpers.h
index 90ca49d..c0235a2 100644
--- a/include/perfetto/ext/ipc/codegen_helpers.h
+++ b/include/perfetto/ext/ipc/codegen_helpers.h
@@ -19,6 +19,12 @@
 #ifndef INCLUDE_PERFETTO_EXT_IPC_CODEGEN_HELPERS_H_
 #define INCLUDE_PERFETTO_EXT_IPC_CODEGEN_HELPERS_H_
 
+#include <memory>
+
+#include "perfetto/ext/ipc/basic_types.h"
+#include "perfetto/ext/ipc/deferred.h"
+#include "perfetto/ext/ipc/service.h"
+
 // A templated protobuf message decoder. Returns nullptr in case of failure.
 template <typename T>
 ::std::unique_ptr<::perfetto::ipc::ProtoMessage> _IPC_Decoder(
diff --git a/include/perfetto/ext/ipc/deferred.h b/include/perfetto/ext/ipc/deferred.h
index 8b58920..aa4cbbd 100644
--- a/include/perfetto/ext/ipc/deferred.h
+++ b/include/perfetto/ext/ipc/deferred.h
@@ -90,7 +90,7 @@
   std::function<void(AsyncResult<ProtoMessage>)> callback_;
 };
 
-template <typename T = ProtoMessage>
+template <typename T>  // T : ProtoMessage subclass
 class Deferred : public DeferredBase {
  public:
   explicit Deferred(std::function<void(AsyncResult<T>)> callback = nullptr) {
diff --git a/include/perfetto/ext/traced/data_source_types.h b/include/perfetto/ext/traced/data_source_types.h
index b7732ca..103fd41 100644
--- a/include/perfetto/ext/traced/data_source_types.h
+++ b/include/perfetto/ext/traced/data_source_types.h
@@ -23,8 +23,6 @@
 #include <set>
 #include <string>
 
-#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
-
 namespace perfetto {
 
 // On ARM, st_ino is not ino_t but unsigned long long.
@@ -33,19 +31,19 @@
 // On ARM, st_dev is not dev_t but unsigned long long.
 using BlockDeviceID = decltype(stat::st_dev);
 
+// From inode_file_map.pbzero.h
+using InodeFileMap_Entry_Type = int32_t;
+
 class InodeMapValue {
  public:
-  InodeMapValue(protos::pbzero::InodeFileMap_Entry_Type entry_type,
-                std::set<std::string> paths)
+  InodeMapValue(InodeFileMap_Entry_Type entry_type, std::set<std::string> paths)
       : entry_type_(entry_type), paths_(std::move(paths)) {}
 
   InodeMapValue() {}
 
-  protos::pbzero::InodeFileMap_Entry_Type type() const { return entry_type_; }
+  InodeFileMap_Entry_Type type() const { return entry_type_; }
   const std::set<std::string>& paths() const { return paths_; }
-  void SetType(protos::pbzero::InodeFileMap_Entry_Type entry_type) {
-    entry_type_ = entry_type;
-  }
+  void SetType(InodeFileMap_Entry_Type entry_type) { entry_type_ = entry_type; }
   void SetPaths(std::set<std::string> paths) { paths_ = std::move(paths); }
   void AddPath(std::string path) { paths_.emplace(std::move(path)); }
 
@@ -54,7 +52,7 @@
   }
 
  private:
-  protos::pbzero::InodeFileMap_Entry_Type entry_type_;
+  InodeFileMap_Entry_Type entry_type_;
   std::set<std::string> paths_;
 };
 
diff --git a/include/perfetto/ext/tracing/core/BUILD.gn b/include/perfetto/ext/tracing/core/BUILD.gn
index 5dc8c88..b263ec0 100644
--- a/include/perfetto/ext/tracing/core/BUILD.gn
+++ b/include/perfetto/ext/tracing/core/BUILD.gn
@@ -28,6 +28,7 @@
     "shared_memory_abi.h",
     "shared_memory_arbiter.h",
     "slice.h",
+    "sliced_protobuf_input_stream.h",
     "startup_trace_writer.h",
     "startup_trace_writer_registry.h",
     "trace_packet.h",
diff --git a/include/perfetto/ext/tracing/core/sliced_protobuf_input_stream.h b/include/perfetto/ext/tracing/core/sliced_protobuf_input_stream.h
new file mode 100644
index 0000000..1a1029c
--- /dev/null
+++ b/include/perfetto/ext/tracing/core/sliced_protobuf_input_stream.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
+#define INCLUDE_PERFETTO_EXT_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
+
+#include "perfetto/ext/tracing/core/slice.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include <google/protobuf/io/zero_copy_stream.h>
+
+#include "perfetto/base/export.h"
+
+namespace perfetto {
+
+using ZeroCopyInputStream = google::protobuf::io::ZeroCopyInputStream;
+
+// Wraps a sequence of Slice(s) in a protobuf ZeroCopyInputStream that can be
+// passed to protobuf::Message::ParseFromZeroCopyStream().
+class PERFETTO_EXPORT SlicedProtobufInputStream : public ZeroCopyInputStream {
+ public:
+  // This indirection deals with the fact that the public protobuf library and
+  // the internal one diverged on this type. The internal doesn's use a custom
+  // defined type. The public one uses a typedef that isn't compatible with
+  // stdint's int64_t (long long vs long). So insted of trying to use
+  // google::protobuf::int64, infer the type from the return value of the
+  // ByteCount().
+  using int64 = decltype(std::declval<ZeroCopyInputStream>().ByteCount());
+
+  explicit SlicedProtobufInputStream(const Slices*);
+  ~SlicedProtobufInputStream() override;
+
+  // ZeroCopyInputStream implementation. See zero_copy_stream.h for the API
+  // contract of the methods below.
+  bool Next(const void** data, int* size) override;
+  void BackUp(int count) override;
+  bool Skip(int count) override;
+  int64 ByteCount() const override;
+
+ private:
+  bool Validate() const;
+
+  const Slices* const slices_;
+  Slices::const_iterator cur_slice_;
+  size_t pos_in_cur_slice_ = 0;
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
diff --git a/include/perfetto/ext/tracing/core/trace_packet.h b/include/perfetto/ext/tracing/core/trace_packet.h
index 80f1018..d695165 100644
--- a/include/perfetto/ext/tracing/core/trace_packet.h
+++ b/include/perfetto/ext/tracing/core/trace_packet.h
@@ -21,7 +21,6 @@
 #include <memory>
 #include <tuple>
 
-#include <google/protobuf/io/zero_copy_stream.h>
 #include "perfetto/base/export.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/tracing/core/slice.h"
@@ -43,7 +42,6 @@
 class PERFETTO_EXPORT TracePacket {
  public:
   using const_iterator = Slices::const_iterator;
-  using ZeroCopyInputStream = ::google::protobuf::io::ZeroCopyInputStream;
 
   // The field id of protos::Trace::packet, static_assert()-ed in the unittest.
   static constexpr uint32_t kPacketFieldNumber = 1;
@@ -56,20 +54,6 @@
   // Accesses all the raw slices in the packet, for saving them to file/network.
   const Slices& slices() const { return slices_; }
 
-  // Decodes the packet. This function requires that the caller:
-  // 1) Does #include "protos/perfetto/trace/trace_packet.pb.h"
-  // 2) Links against the //protos/trace:lite target.
-  // The core service code deliberately doesn't link against that in order to
-  // avoid binary bloat. This is the reason why this is a templated function.
-  // It doesn't need to be (i.e. the caller should not specify the template
-  // argument) but doing so prevents the compiler trying to resolve the
-  // TracePacket type until it's needed, in which case the caller needs (1).
-  template <typename TracePacketType = protos::TracePacket>
-  bool Decode(TracePacketType* packet) const {
-    std::unique_ptr<ZeroCopyInputStream> istr = CreateSlicedInputStream();
-    return packet->ParseFromZeroCopyStream(istr.get());
-  }
-
   // Mutator, used only by the service and tests.
   void AddSlice(Slice);
 
@@ -86,12 +70,14 @@
   // and its size.
   std::tuple<char*, size_t> GetProtoPreamble();
 
+  // Returns the raw protobuf bytes of the slices, all stitched together into
+  // a string. Only for testing.
+  std::string GetRawBytesForTesting();
+
  private:
   TracePacket(const TracePacket&) = delete;
   TracePacket& operator=(const TracePacket&) = delete;
 
-  std::unique_ptr<ZeroCopyInputStream> CreateSlicedInputStream() const;
-
   Slices slices_;     // Not owned.
   size_t size_ = 0;   // SUM(slice.size for slice in slices_).
   char preamble_[8];  // Deliberately not initialized.
diff --git a/infra/ci/config.py b/infra/ci/config.py
index 795ace7..8b5e6fb 100755
--- a/infra/ci/config.py
+++ b/infra/ci/config.py
@@ -79,6 +79,11 @@
         'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_asan=true is_lsan=true',
         'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh',
     },
+    'linux-clang-x86-asan_lsan': {
+        'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_asan=true is_lsan=true '
+                                 'target_cpu="x86"',
+        'PERFETTO_TEST_SCRIPT': 'test/ci/linux_tests.sh',
+    },
     'linux-gcc7-x86_64-release': {
         'PERFETTO_TEST_GN_ARGS': 'is_debug=false is_clang=false ' +
                                  'use_custom_libcxx=false ' +
diff --git a/infra/ci/frontend/Makefile b/infra/ci/frontend/Makefile
index 940f13d..14431aa 100644
--- a/infra/ci/frontend/Makefile
+++ b/infra/ci/frontend/Makefile
@@ -37,23 +37,23 @@
 	../config.py js > $@
 
 static/third_party/xterm-3.14.4.min.css:
-	curl -so $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/xterm.min.css
+	curl -Sso $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/xterm.min.css
 	echo "ad80f73df001c943cfcd98d706dba050704f715d  $@" | ${SHASUM} -c || rm $@
 
 static/third_party/xterm-3.14.4.min.js:
-	curl -so $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/xterm.min.js
+	curl -Sso $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/xterm.min.js
 	echo "9a92b3fbb118fd2a672f7eb4e69598384ca91756  $@" | ${SHASUM} -c || rm $@
 
 static/third_party/xterm-3.14.4-addon-search.min.js:
-	curl -so $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/addons/search/search.min.js
+	curl -Sso $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/addons/search/search.min.js
 	echo "73f55082d00c98b372cac1264ba9da70cdf603d0  $@" | ${SHASUM} -c || rm $@
 
 static/third_party/xterm-3.14.4-addon-fit.min.js:
-	curl -so $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/addons/fit/fit.min.js
+	curl -Sso $@ https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.4/addons/fit/fit.min.js
 	echo "64835b1b71e8ca2d5bbb1a8e3c7f8a8f1edb2e5c  $@" | ${SHASUM} -c || rm $@
 
 static/third_party/mithril-1.1.6.min.js:
-	curl -so $@ https://cdnjs.cloudflare.com/ajax/libs/mithril/1.1.6/mithril.min.js
+	curl -Sso $@ https://cdnjs.cloudflare.com/ajax/libs/mithril/1.1.6/mithril.min.js
 	echo "a204c02ee15c347cf368c3481bdea967b443c8d0  $@" | ${SHASUM} -c || rm $@
 
 static_3p: static/third_party/xterm-3.14.4.min.css static/third_party/xterm-3.14.4.min.js static/third_party/xterm-3.14.4-addon-search.min.js static/third_party/xterm-3.14.4-addon-fit.min.js static/third_party/mithril-1.1.6.min.js
diff --git a/infra/ci/frontend/static/script.js b/infra/ci/frontend/static/script.js
index 6a05246..2be6168 100644
--- a/infra/ci/frontend/static/script.js
+++ b/infra/ci/frontend/static/script.js
@@ -16,12 +16,14 @@
 
 'use strict';
 
+// If you add or remove job types, do not forget to fix the colspans below.
 const JOB_TYPES = [
   { id: 'linux-gcc7-x86_64-release', label: 'rel' },
   { id: 'linux-clang-x86_64-debug', label: 'dbg' },
   { id: 'linux-clang-x86_64-tsan', label: 'tsan' },
   { id: 'linux-clang-x86_64-msan', label: 'msan' },
   { id: 'linux-clang-x86_64-asan_lsan', label: '{a,l}san' },
+  { id: 'linux-clang-x86-asan_lsan', label: 'x86 {a,l}san' },
   { id: 'linux-clang-x86_64-libfuzzer', label: 'fuzzer' },
   { id: 'ui-clang-x86_64-debug', label: 'dbg' },
   { id: 'ui-clang-x86_64-release', label: 'rel' },
@@ -194,15 +196,15 @@
                 m('td[rowspan=4]', 'Status'),
                 m('td[rowspan=4]', 'Owner'),
                 m('td[rowspan=4]', 'Updated'),
-                m('td[colspan=10]', 'Bots'),
+                m('td[colspan=11]', 'Bots'),
               ),
               m('tr',
-                m('td[colspan=8]', 'linux'),
+                m('td[colspan=9]', 'linux'),
                 m('td[colspan=2]', 'android'),
               ),
               m('tr',
                 m('td', 'gcc7'),
-                m('td[colspan=5]', 'clang'),
+                m('td[colspan=6]', 'clang'),
                 m('td[colspan=2]', 'ui'),
                 m('td[colspan=2]', 'clang-arm'),
               ),
@@ -814,4 +816,4 @@
   });
 }
 
-main();
\ No newline at end of file
+main();
diff --git a/infra/ci/frontend/static/third_party/README.md b/infra/ci/frontend/static/third_party/README.md
new file mode 100644
index 0000000..cecb7e7
--- /dev/null
+++ b/infra/ci/frontend/static/third_party/README.md
@@ -0,0 +1 @@
+This file is needed to keep this directory in git.
diff --git a/infra/ci/sandbox/Dockerfile b/infra/ci/sandbox/Dockerfile
index c53169a..ad6782d 100644
--- a/infra/ci/sandbox/Dockerfile
+++ b/infra/ci/sandbox/Dockerfile
@@ -24,7 +24,7 @@
              /etc/apt/sources.list.d/testing.list; \
     apt-get update; \
     apt-get -y install python git curl sudo lz4 tar ccache tini libpulse0 \
-                       libgl1 libxml2; \
+                       libgl1 libxml2 libc6-dev-i386 libtinfo5; \
     apt-get -y -t testing install gcc-7 g++-7 clang; \
     curl https://bootstrap.pypa.io/get-pip.py | python -; \
     pip install --quiet protobuf; \
diff --git a/protos/BUILD b/protos/BUILD
index 107f145..25b1514 100644
--- a/protos/BUILD
+++ b/protos/BUILD
@@ -883,6 +883,65 @@
     ],
 )
 
+# GN target: //protos/perfetto/trace/appended_data:lite_gen
+proto_library(
+    name = "trace_appended_data",
+    srcs = [
+        "perfetto/trace/appended_data/appended_data.proto",
+    ],
+    has_services = 1,
+    cc_api_version = 2,
+    cc_generic_services = 1,
+    visibility = [
+        "//visibility:public",
+    ],
+    deps = [
+        "//third_party/perfetto/protos:trace_profiling",
+    ],
+)
+
+# GN target: //protos/perfetto/trace/appended_data:lite_gen
+cc_proto_library(
+    name = "trace_appended_data_cc_proto",
+    visibility = [
+        "//visibility:public",
+    ],
+    deps = [
+        "//third_party/perfetto/protos:trace_appended_data",
+    ],
+)
+
+# GN target: //protos/perfetto/trace/appended_data:lite_gen
+java_proto_library(
+    name = "trace_appended_data_java_proto",
+    visibility = [
+        "//visibility:public",
+    ],
+    deps = [
+        "//third_party/perfetto/protos:trace_appended_data",
+    ],
+)
+
+# GN target: //protos/perfetto/trace/appended_data:zero_gen
+proto_library(
+    name = "trace_appended_data_zero",
+    srcs = [
+        "perfetto/trace/appended_data/appended_data.proto",
+    ],
+    deps = [
+        "//third_party/perfetto/protos:trace_profiling_zero",
+    ],
+)
+
+# GN target: //protos/perfetto/trace/appended_data:zero_gen
+pbzero_cc_proto_library(
+    name = "trace_appended_data_zero_cc_proto",
+    src_proto_library = "//third_party/perfetto/protos:trace_appended_data_zero",
+    deps = [
+        "//third_party/perfetto:libprotozero",
+    ],
+)
+
 # GN target: //protos/perfetto/trace/chrome:lite_gen
 proto_library(
     name = "trace_chrome",
@@ -1011,7 +1070,6 @@
         "perfetto/trace/ftrace/ftrace_event_bundle.proto",
         "perfetto/trace/ftrace/ftrace_stats.proto",
         "perfetto/trace/ftrace/generic.proto",
-        "perfetto/trace/ftrace/gpu.proto",
         "perfetto/trace/ftrace/i2c.proto",
         "perfetto/trace/ftrace/ipi.proto",
         "perfetto/trace/ftrace/irq.proto",
@@ -1080,7 +1138,6 @@
         "perfetto/trace/ftrace/ftrace_event_bundle.proto",
         "perfetto/trace/ftrace/ftrace_stats.proto",
         "perfetto/trace/ftrace/generic.proto",
-        "perfetto/trace/ftrace/gpu.proto",
         "perfetto/trace/ftrace/i2c.proto",
         "perfetto/trace/ftrace/ipi.proto",
         "perfetto/trace/ftrace/irq.proto",
@@ -1378,6 +1435,7 @@
         "//third_party/perfetto/protos:config_profiling",
         "//third_party/perfetto/protos:config_sys_stats",
         "//third_party/perfetto/protos:trace_android",
+        "//third_party/perfetto/protos:trace_appended_data",
         "//third_party/perfetto/protos:trace_chrome",
         "//third_party/perfetto/protos:trace_filesystem",
         "//third_party/perfetto/protos:trace_ftrace",
@@ -1436,6 +1494,7 @@
         "//third_party/perfetto/protos:config_sys_stats_zero",
         "//third_party/perfetto/protos:config_zero",
         "//third_party/perfetto/protos:trace_android_zero",
+        "//third_party/perfetto/protos:trace_appended_data_zero",
         "//third_party/perfetto/protos:trace_chrome_zero",
         "//third_party/perfetto/protos:trace_filesystem_zero",
         "//third_party/perfetto/protos:trace_ftrace_zero",
@@ -1573,7 +1632,6 @@
     name = "trace_processor",
     srcs = [
         "perfetto/trace_processor/raw_query.proto",
-        "perfetto/trace_processor/sched.proto",
         "perfetto/trace_processor/trace_processor.proto",
     ],
     has_services = 1,
diff --git a/protos/perfetto/common/gpu_counter_descriptor.proto b/protos/perfetto/common/gpu_counter_descriptor.proto
index 03de9db..e15a6f3 100644
--- a/protos/perfetto/common/gpu_counter_descriptor.proto
+++ b/protos/perfetto/common/gpu_counter_descriptor.proto
@@ -26,13 +26,14 @@
     optional uint32 counter_id = 1;
     optional string name = 2;
     optional string description = 3;
-    reserved 4;
+    reserved 4;  // MeasureUnit unit (deprecated)
     oneof peak_value {
       int64 int_peak_value = 5;
       double double_peak_value = 6;
     };
     repeated MeasureUnit numerator_units = 7;
     repeated MeasureUnit denominator_units = 8;
+    optional bool select_by_default = 9;
   }
   repeated GpuCounterSpec specs = 1;
 
@@ -52,6 +53,15 @@
   }
   repeated GpuCounterBlock blocks = 2;
 
+  // optional.  Minimum sampling period supported by the producer in nanoseconds.
+  optional uint64 min_sampling_period_ns = 3;
+
+  // optional.  Maximum sampling period supported by the producer in nanoseconds.
+  optional uint64 max_sampling_period_ns = 4;
+
+  // optional.  The producer supports counter sampling by instrumenting the command buffer.
+  optional bool supports_instrumented_sampling = 5;
+
   enum MeasureUnit {
     NONE = 0;
 
diff --git a/protos/perfetto/config/gpu/gpu_counter_config.proto b/protos/perfetto/config/gpu/gpu_counter_config.proto
index 8f499b4..6e448af 100644
--- a/protos/perfetto/config/gpu/gpu_counter_config.proto
+++ b/protos/perfetto/config/gpu/gpu_counter_config.proto
@@ -29,4 +29,10 @@
   // List of counters to be sampled. Counter IDs correspond to the ones
   // described in GpuCounterSpec in the data source descriptor.
   repeated uint32 counter_ids = 2;
+
+  // Sample counters by instrumenting command buffers.
+  optional bool instrumented_sampling = 3;
+
+  // Fix gpu clock rate during trace session.
+  optional bool fix_gpu_clock = 4;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 3a5f155..259a8b0 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -81,13 +81,14 @@
     optional uint32 counter_id = 1;
     optional string name = 2;
     optional string description = 3;
-    reserved 4;
+    reserved 4;  // MeasureUnit unit (deprecated)
     oneof peak_value {
       int64 int_peak_value = 5;
       double double_peak_value = 6;
     };
     repeated MeasureUnit numerator_units = 7;
     repeated MeasureUnit denominator_units = 8;
+    optional bool select_by_default = 9;
   }
   repeated GpuCounterSpec specs = 1;
 
@@ -107,6 +108,15 @@
   }
   repeated GpuCounterBlock blocks = 2;
 
+  // optional.  Minimum sampling period supported by the producer in nanoseconds.
+  optional uint64 min_sampling_period_ns = 3;
+
+  // optional.  Maximum sampling period supported by the producer in nanoseconds.
+  optional uint64 max_sampling_period_ns = 4;
+
+  // optional.  The producer supports counter sampling by instrumenting the command buffer.
+  optional bool supports_instrumented_sampling = 5;
+
   enum MeasureUnit {
     NONE = 0;
 
@@ -1256,6 +1266,12 @@
   // List of counters to be sampled. Counter IDs correspond to the ones
   // described in GpuCounterSpec in the data source descriptor.
   repeated uint32 counter_ids = 2;
+
+  // Sample counters by instrumenting command buffers.
+  optional bool instrumented_sampling = 3;
+
+  // Fix gpu clock rate during trace session.
+  optional bool fix_gpu_clock = 4;
 }
 
 // End of protos/perfetto/config/gpu/gpu_counter_config.proto
diff --git a/protos/perfetto/ipc/BUILD.gn b/protos/perfetto/ipc/BUILD.gn
index a3e0bfd..48f54a1 100644
--- a/protos/perfetto/ipc/BUILD.gn
+++ b/protos/perfetto/ipc/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("../../../gn/ipc_library.gni")
 import("../../../gn/perfetto.gni")
+import("../../../gn/proto_library.gni")
 
 # IPC service definitions.
 ipc_library("ipc") {
@@ -28,3 +29,10 @@
     "producer_port.proto",
   ]
 }
+
+perfetto_proto_library("wire_protocol") {
+  proto_generators = [ "lite" ]
+  sources = [
+    "wire_protocol.proto",
+  ]
+}
diff --git a/src/ipc/wire_protocol.proto b/protos/perfetto/ipc/wire_protocol.proto
similarity index 100%
rename from src/ipc/wire_protocol.proto
rename to protos/perfetto/ipc/wire_protocol.proto
diff --git a/protos/perfetto/metrics/android/cpu_metric.proto b/protos/perfetto/metrics/android/cpu_metric.proto
index 2be68a9..172a6d8 100644
--- a/protos/perfetto/metrics/android/cpu_metric.proto
+++ b/protos/perfetto/metrics/android/cpu_metric.proto
@@ -36,12 +36,15 @@
   message Thread {
     optional string name = 1;
     repeated CpuFrequencyData cpu = 2;
+    // CPU cycles normalized at 1Ghz frequency.
+    optional int64 normalized_cpu_cycles = 3;
   }
 
   // Threads (name and CPU frequency) per process.
   message Process {
     optional string name = 1;
     repeated Thread threads = 2;
+    optional int64 normalized_cpu_cycles = 3;
   }
 
   // Process name and CPU frequency data aggregated by thread and cpu.
diff --git a/protos/perfetto/trace/BUILD.gn b/protos/perfetto/trace/BUILD.gn
index 9f0e1c3..65a364a 100644
--- a/protos/perfetto/trace/BUILD.gn
+++ b/protos/perfetto/trace/BUILD.gn
@@ -70,6 +70,7 @@
     ":minimal_@TYPE@",
     "../config:@TYPE@",
     "android:@TYPE@",
+    "appended_data:@TYPE@",
     "chrome:@TYPE@",
     "filesystem:@TYPE@",
     "ftrace:@TYPE@",
diff --git a/protos/perfetto/trace/appended_data/BUILD.gn b/protos/perfetto/trace/appended_data/BUILD.gn
new file mode 100644
index 0000000..16c3bc2
--- /dev/null
+++ b/protos/perfetto/trace/appended_data/BUILD.gn
@@ -0,0 +1,24 @@
+# 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("../../../../gn/proto_library.gni")
+
+perfetto_proto_library("@TYPE@") {
+  sources = [
+    "appended_data.proto",
+  ]
+  deps = [
+    "../profiling:@TYPE@",
+  ]
+}
diff --git a/protos/perfetto/trace/appended_data/appended_data.proto b/protos/perfetto/trace/appended_data/appended_data.proto
new file mode 100644
index 0000000..777129a
--- /dev/null
+++ b/protos/perfetto/trace/appended_data/appended_data.proto
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+import "protos/perfetto/trace/profiling/profile_common.proto";
+
+package perfetto.protos;
+
+// Contains data appended to the trace after the fact. Elements referring to
+// past interned data must have a matching trusted_packet_sequence_id.
+//
+// The writer will usually parse the trace, resolve and concatenate
+// AppendedData (and corresponding InternedData) to the end of the trace.
+// Any InternedData must precede or be on the same packet as the AppendedData
+// that uses them.
+//
+// Next id: 2.
+message AppendedData {
+  // Maps between interned frames and their resolved symbols.
+  repeated ProfiledFrameSymbols profiled_frame_symbols = 1;
+}
diff --git a/protos/perfetto/trace/ftrace/all_protos.gni b/protos/perfetto/trace/ftrace/all_protos.gni
index 3a9b0e7..b6278b8 100644
--- a/protos/perfetto/trace/ftrace/all_protos.gni
+++ b/protos/perfetto/trace/ftrace/all_protos.gni
@@ -30,7 +30,6 @@
   "fence.proto",
   "filemap.proto",
   "ftrace.proto",
-  "gpu.proto",
   "i2c.proto",
   "ipi.proto",
   "irq.proto",
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index 53c94d4..c953f36 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -15,7 +15,6 @@
 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/gpu.proto";
 import "protos/perfetto/trace/ftrace/i2c.proto";
 import "protos/perfetto/trace/ftrace/ipi.proto";
 import "protos/perfetto/trace/ftrace/irq.proto";
@@ -396,8 +395,5 @@
     SysExitFtraceEvent sys_exit = 330;
     ZeroFtraceEvent zero = 331;
     GpuFrequencyFtraceEvent gpu_frequency = 332;
-    GpuSchedEnqueueFtraceEvent gpu_sched_enqueue = 333;
-    GpuSchedSubmitFtraceEvent gpu_sched_submit = 334;
-    GpuSchedCompleteFtraceEvent gpu_sched_complete = 335;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/gpu.proto b/protos/perfetto/trace/ftrace/gpu.proto
deleted file mode 100644
index 033d402..0000000
--- a/protos/perfetto/trace/ftrace/gpu.proto
+++ /dev/null
@@ -1,28 +0,0 @@
-// Autogenerated by:
-// ../../tools/ftrace_proto_gen/ftrace_proto_gen.cc
-// Do not edit.
-
-syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
-package perfetto.protos;
-
-message GpuSchedEnqueueFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 job_id = 2;
-  optional uint32 priority = 3;
-  optional uint32 submission_id = 4;
-}
-message GpuSchedSubmitFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 hwqueue_id = 2;
-  optional uint32 job_id = 3;
-  optional uint32 priority = 4;
-  optional uint32 submission_id = 5;
-}
-message GpuSchedCompleteFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 job_id = 2;
-  optional string msg = 3;
-  optional uint32 priority = 4;
-  optional uint32 submission_id = 5;
-}
diff --git a/protos/perfetto/trace/gpu/gpu_counter_event.proto b/protos/perfetto/trace/gpu/gpu_counter_event.proto
index 53574c3..fe3a8f0 100644
--- a/protos/perfetto/trace/gpu/gpu_counter_event.proto
+++ b/protos/perfetto/trace/gpu/gpu_counter_event.proto
@@ -35,4 +35,7 @@
     }
   }
   repeated GpuCounter counters = 2;
+
+  // optional. Identifier for GPU in a multi-gpu device.
+  optional int32 gpu_id = 3;
 }
diff --git a/protos/perfetto/trace/gpu/gpu_render_stage_event.proto b/protos/perfetto/trace/gpu/gpu_render_stage_event.proto
index 589f6b7..a2a4cfd 100644
--- a/protos/perfetto/trace/gpu/gpu_render_stage_event.proto
+++ b/protos/perfetto/trace/gpu/gpu_render_stage_event.proto
@@ -24,8 +24,8 @@
   // required. Unique ID for the event.
   optional uint64 event_id = 1;
 
-  // optional. Duration in GPU clock.  If unset, this is a single time point
-  // event.
+  // optional. Duration of the event.  This should be in the same clock domain as the timestamp of
+  // the packet.  If unset, this is a single time point event.
   optional uint64 duration = 2;
 
   // required. ID to a hardware queue description in the specifications.
@@ -37,10 +37,10 @@
   // required. GL context/VK device.
   optional uint64 context = 5;
 
-  // optional. The surface or render target for this event.
-  optional uint64 surface_id = 8;
+  // optional. The render target for this event.
+  optional uint64 render_target_handle = 8;
 
-  // optional. Render pass handle.
+  // optional. The Vulkan render pass handle.
   optional uint64 render_pass_handle = 9;
 
   // optional. Submission ID generated by the UMD.
@@ -68,21 +68,17 @@
       optional string description = 2;
     }
 
-    // Labels to categorize the hw Queue this event goes on
+    // Labels to categorize the hw Queue this event goes on.
     repeated Description hw_queue = 2;
 
-    // Labels to categorize render stage(binning, render, compute etc)
+    // Labels to categorize render stage(binning, render, compute etc).
     repeated Description stage = 3;
   }
   optional Specifications specifications = 7;
 
-  // optional.  If True, duration must not be set and a GpuRenderStageEndEvent is expected.
-  optional bool begin_event = 11;
+  // optional. Identifier for GPU in a multi-gpu device.
+  optional int32 gpu_id = 11;
 
   // Extension for vendor's custom proto.
   extensions 100;
 }
-
-// Message to signal the end of the last render stage event that had set begin_event.
-message GpuRenderStageEndEvent {
-}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 151424f..1c24dd5 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -81,13 +81,14 @@
     optional uint32 counter_id = 1;
     optional string name = 2;
     optional string description = 3;
-    reserved 4;
+    reserved 4;  // MeasureUnit unit (deprecated)
     oneof peak_value {
       int64 int_peak_value = 5;
       double double_peak_value = 6;
     };
     repeated MeasureUnit numerator_units = 7;
     repeated MeasureUnit denominator_units = 8;
+    optional bool select_by_default = 9;
   }
   repeated GpuCounterSpec specs = 1;
 
@@ -107,6 +108,15 @@
   }
   repeated GpuCounterBlock blocks = 2;
 
+  // optional.  Minimum sampling period supported by the producer in nanoseconds.
+  optional uint64 min_sampling_period_ns = 3;
+
+  // optional.  Maximum sampling period supported by the producer in nanoseconds.
+  optional uint64 max_sampling_period_ns = 4;
+
+  // optional.  The producer supports counter sampling by instrumenting the command buffer.
+  optional bool supports_instrumented_sampling = 5;
+
   enum MeasureUnit {
     NONE = 0;
 
@@ -615,6 +625,24 @@
 
 // End of protos/perfetto/trace/android/packages_list.proto
 
+// Begin of protos/perfetto/trace/appended_data/appended_data.proto
+
+// Contains data appended to the trace after the fact. Elements referring to
+// past interned data must have a matching trusted_packet_sequence_id.
+//
+// The writer will usually parse the trace, resolve and concatenate
+// AppendedData (and corresponding InternedData) to the end of the trace.
+// Any InternedData must precede or be on the same packet as the AppendedData
+// that uses them.
+//
+// Next id: 2.
+message AppendedData {
+  // Maps between interned frames and their resolved symbols.
+  repeated ProfiledFrameSymbols profiled_frame_symbols = 1;
+}
+
+// End of protos/perfetto/trace/appended_data/appended_data.proto
+
 // Begin of protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto
 
 // This message is not intended to be written by the chrome on the device.
@@ -2277,9 +2305,6 @@
     SysExitFtraceEvent sys_exit = 330;
     ZeroFtraceEvent zero = 331;
     GpuFrequencyFtraceEvent gpu_frequency = 332;
-    GpuSchedEnqueueFtraceEvent gpu_sched_enqueue = 333;
-    GpuSchedSubmitFtraceEvent gpu_sched_submit = 334;
-    GpuSchedCompleteFtraceEvent gpu_sched_complete = 335;
   }
 }
 
@@ -2374,31 +2399,6 @@
 
 // End of protos/perfetto/trace/ftrace/generic.proto
 
-// Begin of protos/perfetto/trace/ftrace/gpu.proto
-
-message GpuSchedEnqueueFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 job_id = 2;
-  optional uint32 priority = 3;
-  optional uint32 submission_id = 4;
-}
-message GpuSchedSubmitFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 hwqueue_id = 2;
-  optional uint32 job_id = 3;
-  optional uint32 priority = 4;
-  optional uint32 submission_id = 5;
-}
-message GpuSchedCompleteFtraceEvent {
-  optional uint32 ctx_id = 1;
-  optional uint32 job_id = 2;
-  optional string msg = 3;
-  optional uint32 priority = 4;
-  optional uint32 submission_id = 5;
-}
-
-// End of protos/perfetto/trace/ftrace/gpu.proto
-
 // Begin of protos/perfetto/trace/ftrace/kmem.proto
 
 message AllocPagesIommuEndFtraceEvent {
@@ -3403,7 +3403,7 @@
 // TracePacket(s).
 //
 // Next reserved id: 13 (up to 15).
-// Next id: 61.
+// Next id: 62.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -3506,6 +3506,11 @@
   // proactively in advance of referring to them in later packets.
   optional InternedData interned_data = 12;
 
+  // Data appended to the trace post-processing (e.g. symbol information). When
+  // the AppendedData contents refer to interned data, they must share the same
+  // |trusted_packet_sequence_id|.
+  optional AppendedData appended_data = 61;
+
   enum SequenceFlags {
     SEQ_UNSPECIFIED = 0;
 
@@ -4054,6 +4059,9 @@
     }
   }
   repeated GpuCounter counters = 2;
+
+  // optional. Identifier for GPU in a multi-gpu device.
+  optional int32 gpu_id = 3;
 }
 
 // End of protos/perfetto/trace/gpu/gpu_counter_event.proto
@@ -4065,8 +4073,8 @@
   // required. Unique ID for the event.
   optional uint64 event_id = 1;
 
-  // optional. Duration in GPU clock.  If unset, this is a single time point
-  // event.
+  // optional. Duration of the event.  This should be in the same clock domain as the timestamp of
+  // the packet.  If unset, this is a single time point event.
   optional uint64 duration = 2;
 
   // required. ID to a hardware queue description in the specifications.
@@ -4078,10 +4086,10 @@
   // required. GL context/VK device.
   optional uint64 context = 5;
 
-  // optional. The surface or render target for this event.
-  optional uint64 surface_id = 8;
+  // optional. The render target for this event.
+  optional uint64 render_target_handle = 8;
 
-  // optional. Render pass handle.
+  // optional. The Vulkan render pass handle.
   optional uint64 render_pass_handle = 9;
 
   // optional. Submission ID generated by the UMD.
@@ -4109,25 +4117,21 @@
       optional string description = 2;
     }
 
-    // Labels to categorize the hw Queue this event goes on
+    // Labels to categorize the hw Queue this event goes on.
     repeated Description hw_queue = 2;
 
-    // Labels to categorize render stage(binning, render, compute etc)
+    // Labels to categorize render stage(binning, render, compute etc).
     repeated Description stage = 3;
   }
   optional Specifications specifications = 7;
 
-  // optional.  If True, duration must not be set and a GpuRenderStageEndEvent is expected.
-  optional bool begin_event = 11;
+  // optional. Identifier for GPU in a multi-gpu device.
+  optional int32 gpu_id = 11;
 
   // Extension for vendor's custom proto.
   extensions 100;
 }
 
-// Message to signal the end of the last render stage event that had set begin_event.
-message GpuRenderStageEndEvent {
-}
-
 // End of protos/perfetto/trace/gpu/gpu_render_stage_event.proto
 
 // Begin of protos/perfetto/config/android/android_log_config.proto
@@ -4887,6 +4891,12 @@
   // List of counters to be sampled. Counter IDs correspond to the ones
   // described in GpuCounterSpec in the data source descriptor.
   repeated uint32 counter_ids = 2;
+
+  // Sample counters by instrumenting command buffers.
+  optional bool instrumented_sampling = 3;
+
+  // Fix gpu clock rate during trace session.
+  optional bool fix_gpu_clock = 4;
 }
 
 // End of protos/perfetto/config/gpu/gpu_counter_config.proto
diff --git a/protos/perfetto/trace/trace_packet.proto b/protos/perfetto/trace/trace_packet.proto
index c050a5c..0afaf8b 100644
--- a/protos/perfetto/trace/trace_packet.proto
+++ b/protos/perfetto/trace/trace_packet.proto
@@ -22,6 +22,7 @@
 import "protos/perfetto/trace/android/android_log.proto";
 import "protos/perfetto/trace/android/graphics_frame_event.proto";
 import "protos/perfetto/trace/android/packages_list.proto";
+import "protos/perfetto/trace/appended_data/appended_data.proto";
 import "protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto";
 import "protos/perfetto/trace/chrome/chrome_metadata.proto";
 import "protos/perfetto/trace/chrome/chrome_trace_event.proto";
@@ -56,7 +57,7 @@
 // TracePacket(s).
 //
 // Next reserved id: 13 (up to 15).
-// Next id: 61.
+// Next id: 62.
 message TracePacket {
   // The timestamp of the TracePacket.
   // By default this timestamps refers to the trace clock (CLOCK_BOOTTIME on
@@ -159,6 +160,11 @@
   // proactively in advance of referring to them in later packets.
   optional InternedData interned_data = 12;
 
+  // Data appended to the trace post-processing (e.g. symbol information). When
+  // the AppendedData contents refer to interned data, they must share the same
+  // |trusted_packet_sequence_id|.
+  optional AppendedData appended_data = 61;
+
   enum SequenceFlags {
     SEQ_UNSPECIFIED = 0;
 
diff --git a/protos/perfetto/trace_processor/proto_files.gni b/protos/perfetto/trace_processor/proto_files.gni
index 20d1529..2b7d53e 100644
--- a/protos/perfetto/trace_processor/proto_files.gni
+++ b/protos/perfetto/trace_processor/proto_files.gni
@@ -15,7 +15,6 @@
 # This variable is used both by ./BUILD.gn (for the C++ proto codegen) and by
 # //ui/BUIlD.gn (for the TypeScript/JS proto codegen).
 trace_processor_protos = [
-  "sched",
   "raw_query",
   "trace_processor",
 ]
diff --git a/protos/perfetto/trace_processor/sched.proto b/src/android_internal/empty_file.cc
similarity index 67%
rename from protos/perfetto/trace_processor/sched.proto
rename to src/android_internal/empty_file.cc
index ba59999..22ffa8f 100644
--- a/protos/perfetto/trace_processor/sched.proto
+++ b/src/android_internal/empty_file.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
+// TODO(primiano): this file is here only to have one translation unit for the
+// temporary perfetto_src_tracing_ipc target.
 
-package perfetto.protos;
-
-message Sched {
-  // TODO(primiano): fill in next CLs.
-  optional string test = 1;
-}
+__attribute__((visibility("default"))) void PerfettoNoOp();
+void PerfettoNoOp() {}
diff --git a/src/base/string_utils.cc b/src/base/string_utils.cc
index e5b7c4e..c042290 100644
--- a/src/base/string_utils.cc
+++ b/src/base/string_utils.cc
@@ -16,6 +16,8 @@
 
 #include "perfetto/ext/base/string_utils.h"
 
+#include <string.h>
+
 #include <algorithm>
 
 #include "perfetto/base/logging.h"
@@ -39,8 +41,9 @@
 
 bool CaseInsensitiveEqual(const std::string& first, const std::string& second) {
   return first.size() == second.size() &&
-         std::equal(first.begin(), first.end(), second.begin(),
-                    [](char a, char b) { return tolower(a) == tolower(b); });
+         std::equal(
+             first.begin(), first.end(), second.begin(),
+             [](char a, char b) { return Lowercase(a) == Lowercase(b); });
 }
 
 std::string Join(const std::vector<std::string>& parts,
@@ -64,7 +67,8 @@
   size_t next;
   for (;;) {
     next = std::min(text.find(delimiter, start), text.size());
-    output.emplace_back(&text[start], next - start);
+    if (next > start)
+      output.emplace_back(&text[start], next - start);
     start = next + delimiter.size();
     if (start >= text.size())
       break;
@@ -72,5 +76,47 @@
   return output;
 }
 
+std::string StripPrefix(const std::string& str, const std::string& prefix) {
+  return StartsWith(str, prefix) ? str.substr(prefix.size()) : str;
+}
+
+std::string StripSuffix(const std::string& str, const std::string& suffix) {
+  return EndsWith(str, suffix) ? str.substr(0, str.size() - suffix.size())
+                               : str;
+}
+
+std::string ToUpper(const std::string& str) {
+  // Don't use toupper(), it depends on the locale.
+  std::string res(str);
+  auto end = res.end();
+  for (auto c = res.begin(); c != end; ++c)
+    *c = ('a' <= *c && *c <= 'z') ? (*c += 'A' - 'a') : *c;
+  return res;
+}
+
+std::string ToHex(const char* data, size_t size) {
+  std::string hex(2 * size + 1, 'x');
+  for (size_t i = 0; i < size; ++i) {
+    // snprintf prints 3 characters, the two hex digits and a null byte. As we
+    // write left to write, we keep overwriting the nullbytes, except for the
+    // last call to snprintf.
+    snprintf(&(hex[2 * i]), 3, "%02hhx", data[i]);
+  }
+  // Remove the trailing nullbyte produced by the last snprintf.
+  hex.resize(2 * size);
+  return hex;
+}
+
+std::string StripChars(const std::string& str,
+                       const std::string& chars,
+                       char replacement) {
+  std::string res(str);
+  const char* start = res.c_str();
+  const char* remove = chars.c_str();
+  for (const char* c = strpbrk(start, remove); c; c = strpbrk(c + 1, remove))
+    res[static_cast<uintptr_t>(c - start)] = replacement;
+  return res;
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/string_utils_unittest.cc b/src/base/string_utils_unittest.cc
index 58d0262..c14415c 100644
--- a/src/base/string_utils_unittest.cc
+++ b/src/base/string_utils_unittest.cc
@@ -23,6 +23,22 @@
 
 using testing::ElementsAre;
 
+TEST(StringUtilsTest, Lowercase) {
+  EXPECT_EQ(Lowercase('A'), 'a');
+  EXPECT_EQ(Lowercase('a'), 'a');
+  EXPECT_EQ(Lowercase('Z'), 'z');
+  EXPECT_EQ(Lowercase('z'), 'z');
+  EXPECT_EQ(Lowercase('!'), '!');
+}
+
+TEST(StringUtilsTest, Uppercase) {
+  EXPECT_EQ(Uppercase('A'), 'A');
+  EXPECT_EQ(Uppercase('a'), 'A');
+  EXPECT_EQ(Uppercase('Z'), 'Z');
+  EXPECT_EQ(Uppercase('z'), 'Z');
+  EXPECT_EQ(Uppercase('!'), '!');
+}
+
 TEST(StringUtilsTest, StartsWith) {
   EXPECT_TRUE(StartsWith("", ""));
   EXPECT_TRUE(StartsWith("abc", ""));
@@ -45,6 +61,11 @@
   EXPECT_FALSE(EndsWith("", "c"));
 }
 
+TEST(StringUtilsTest, ToHex) {
+  EXPECT_EQ(ToHex(""), "");
+  EXPECT_EQ(ToHex("abc123"), "616263313233");
+}
+
 TEST(StringUtilsTest, CaseInsensitiveEqual) {
   EXPECT_TRUE(CaseInsensitiveEqual("", ""));
   EXPECT_TRUE(CaseInsensitiveEqual("abc", "abc"));
@@ -55,15 +76,38 @@
 }
 
 TEST(StringUtilsTest, SplitString) {
-  EXPECT_THAT(SplitString("", ":"), ElementsAre(""));
+  EXPECT_THAT(SplitString("", ":"), ElementsAre());
   EXPECT_THAT(SplitString("a:b:c", ":"), ElementsAre("a", "b", "c"));
   EXPECT_THAT(SplitString("a::b::c", "::"), ElementsAre("a", "b", "c"));
+  EXPECT_THAT(SplitString("::::a::b::::c::", "::"), ElementsAre("a", "b", "c"));
   EXPECT_THAT(SplitString("abc", ":"), ElementsAre("abc"));
   EXPECT_THAT(SplitString("abc", "::"), ElementsAre("abc"));
   EXPECT_THAT(SplitString("abc", ":"), ElementsAre("abc"));
   EXPECT_THAT(SplitString("abc", "::"), ElementsAre("abc"));
 }
 
+TEST(StringUtilsTest, Strip) {
+  EXPECT_EQ(StripPrefix("abc", ""), "abc");
+  EXPECT_EQ(StripPrefix("abc", "a"), "bc");
+  EXPECT_EQ(StripPrefix("abc", "ab"), "c");
+  EXPECT_EQ(StripPrefix("abc", "abc"), "");
+  EXPECT_EQ(StripPrefix("abc", "abcd"), "abc");
+
+  EXPECT_EQ(StripSuffix("abc", ""), "abc");
+  EXPECT_EQ(StripSuffix("abc", "c"), "ab");
+  EXPECT_EQ(StripSuffix("abc", "bc"), "a");
+  EXPECT_EQ(StripSuffix("abc", "abc"), "");
+  EXPECT_EQ(StripSuffix("abc", "ebcd"), "abc");
+
+  EXPECT_EQ(StripChars("foobar", "", '_'), "foobar");
+  EXPECT_EQ(StripChars("foobar", "x", '_'), "foobar");
+  EXPECT_EQ(StripChars("foobar", "f", '_'), "_oobar");
+  EXPECT_EQ(StripChars("foobar", "o", '_'), "f__bar");
+  EXPECT_EQ(StripChars("foobar", "oa", '_'), "f__b_r");
+  EXPECT_EQ(StripChars("foobar", "fbr", '_'), "_oo_a_");
+  EXPECT_EQ(StripChars("foobar", "froab", '_'), "______");
+}
+
 }  // namespace
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/ipc/BUILD.gn b/src/ipc/BUILD.gn
index e476e1c..12353de 100644
--- a/src/ipc/BUILD.gn
+++ b/src/ipc/BUILD.gn
@@ -29,8 +29,8 @@
     "../base:unix_socket",
   ]
   deps = [
-    ":wire_protocol",
     "../../gn:default_deps",
+    "../../protos/perfetto/ipc:wire_protocol",
     "../base",
   ]
   sources = [
@@ -52,8 +52,8 @@
   ]
   deps = [
     ":ipc",
-    ":wire_protocol",
     "../../gn:default_deps",
+    "../../protos/perfetto/ipc:wire_protocol",
   ]
 }
 
@@ -62,9 +62,9 @@
   deps = [
     ":ipc",
     ":test_messages",
-    ":wire_protocol",
     "../../gn:default_deps",
     "../../gn:gtest_and_gmock",
+    "../../protos/perfetto/ipc:wire_protocol",
     "../base",
     "../base:test_support",
   ]
@@ -77,14 +77,6 @@
   ]
 }
 
-perfetto_proto_library("wire_protocol") {
-  proto_generators = [ "lite" ]
-  sources = [
-    "wire_protocol.proto",
-  ]
-  proto_path = perfetto_root_path
-}
-
 ipc_library("test_messages") {
   sources = [
     "test/client_unittest_messages.proto",
diff --git a/src/ipc/buffered_frame_deserializer.cc b/src/ipc/buffered_frame_deserializer.cc
index 290db65..dc107f7 100644
--- a/src/ipc/buffered_frame_deserializer.cc
+++ b/src/ipc/buffered_frame_deserializer.cc
@@ -22,11 +22,10 @@
 #include <type_traits>
 #include <utility>
 
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
 
-#include "src/ipc/wire_protocol.pb.h"
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 
 namespace perfetto {
 namespace ipc {
@@ -167,9 +166,7 @@
   if (size == 0)
     return;
   std::unique_ptr<Frame> frame(new Frame);
-  const int sz = static_cast<int>(size);
-  ::google::protobuf::io::ArrayInputStream stream(data, sz);
-  if (frame->ParseFromBoundedZeroCopyStream(&stream, sz))
+  if (frame->ParseFromArray(data, static_cast<int>(size)))
     decoded_frames_.push_back(std::move(frame));
 }
 
diff --git a/src/ipc/buffered_frame_deserializer_fuzzer.cc b/src/ipc/buffered_frame_deserializer_fuzzer.cc
index c3b22bb..61a818d 100644
--- a/src/ipc/buffered_frame_deserializer_fuzzer.cc
+++ b/src/ipc/buffered_frame_deserializer_fuzzer.cc
@@ -19,7 +19,8 @@
 
 #include "perfetto/ext/base/utils.h"
 #include "src/ipc/buffered_frame_deserializer.h"
-#include "src/ipc/wire_protocol.pb.h"
+
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
diff --git a/src/ipc/buffered_frame_deserializer_unittest.cc b/src/ipc/buffered_frame_deserializer_unittest.cc
index 727ee7e..792365b 100644
--- a/src/ipc/buffered_frame_deserializer_unittest.cc
+++ b/src/ipc/buffered_frame_deserializer_unittest.cc
@@ -23,7 +23,7 @@
 #include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
 
-#include "src/ipc/wire_protocol.pb.h"
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 
 namespace perfetto {
 namespace ipc {
diff --git a/src/ipc/client_impl.cc b/src/ipc/client_impl.cc
index ff0dca2..c3bd830 100644
--- a/src/ipc/client_impl.cc
+++ b/src/ipc/client_impl.cc
@@ -43,7 +43,6 @@
 
 ClientImpl::ClientImpl(const char* socket_name, base::TaskRunner* task_runner)
     : task_runner_(task_runner), weak_ptr_factory_(this) {
-  GOOGLE_PROTOBUF_VERIFY_VERSION;
   sock_ = base::UnixSocket::Connect(socket_name, this, task_runner);
 }
 
diff --git a/src/ipc/client_impl.h b/src/ipc/client_impl.h
index db65f02..71c211f 100644
--- a/src/ipc/client_impl.h
+++ b/src/ipc/client_impl.h
@@ -23,7 +23,7 @@
 #include "perfetto/ext/ipc/client.h"
 #include "src/ipc/buffered_frame_deserializer.h"
 
-#include "src/ipc/wire_protocol.pb.h"
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 
 #include <list>
 #include <map>
diff --git a/src/ipc/deferred.cc b/src/ipc/deferred.cc
index 2bc12ec..c16604a 100644
--- a/src/ipc/deferred.cc
+++ b/src/ipc/deferred.cc
@@ -16,8 +16,6 @@
 
 #include "perfetto/ext/ipc/deferred.h"
 
-#include <google/protobuf/message_lite.h>
-
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
 
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index 6895424..662e6e6 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -26,7 +26,7 @@
 #include "perfetto/ext/ipc/service.h"
 #include "perfetto/ext/ipc/service_descriptor.h"
 
-#include "src/ipc/wire_protocol.pb.h"
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 
 // TODO(primiano): put limits on #connections/uid and req. queue (b/69093705).
 
@@ -54,14 +54,12 @@
 
 HostImpl::HostImpl(base::ScopedFile socket_fd, base::TaskRunner* task_runner)
     : task_runner_(task_runner), weak_ptr_factory_(this) {
-  GOOGLE_PROTOBUF_VERIFY_VERSION;
   PERFETTO_DCHECK_THREAD(thread_checker_);
   sock_ = base::UnixSocket::Listen(std::move(socket_fd), this, task_runner_);
 }
 
 HostImpl::HostImpl(const char* socket_name, base::TaskRunner* task_runner)
     : task_runner_(task_runner), weak_ptr_factory_(this) {
-  GOOGLE_PROTOBUF_VERIFY_VERSION;
   PERFETTO_DCHECK_THREAD(thread_checker_);
   sock_ = base::UnixSocket::Listen(socket_name, this, task_runner_);
 }
diff --git a/src/ipc/host_impl_unittest.cc b/src/ipc/host_impl_unittest.cc
index 025d4c1..70215ff 100644
--- a/src/ipc/host_impl_unittest.cc
+++ b/src/ipc/host_impl_unittest.cc
@@ -30,8 +30,8 @@
 #include "src/ipc/test/test_socket.h"
 #include "test/gtest_and_gmock.h"
 
+#include "protos/perfetto/ipc/wire_protocol.pb.h"
 #include "src/ipc/test/client_unittest_messages.pb.h"
-#include "src/ipc/wire_protocol.pb.h"
 
 namespace perfetto {
 namespace ipc {
diff --git a/src/ipc/protoc_plugin/BUILD.gn b/src/ipc/protoc_plugin/BUILD.gn
index 4478f60..535f53a 100644
--- a/src/ipc/protoc_plugin/BUILD.gn
+++ b/src/ipc/protoc_plugin/BUILD.gn
@@ -21,5 +21,6 @@
   deps = [
     "../../../gn:default_deps",
     "../../../gn:protoc_lib",
+    "../../../src/base",
   ]
 }
diff --git a/src/ipc/protoc_plugin/ipc_plugin.cc b/src/ipc/protoc_plugin/ipc_plugin.cc
index 064e4cd..f3a4372 100644
--- a/src/ipc/protoc_plugin/ipc_plugin.cc
+++ b/src/ipc/protoc_plugin/ipc_plugin.cc
@@ -20,13 +20,13 @@
 #include <string>
 
 #include <google/protobuf/compiler/code_generator.h>
-#include <google/protobuf/compiler/cpp/cpp_options.h>
 #include <google/protobuf/compiler/plugin.h>
 #include <google/protobuf/descriptor.h>
 #include <google/protobuf/descriptor.pb.h>
 #include <google/protobuf/io/printer.h>
 #include <google/protobuf/io/zero_copy_stream.h>
-#include <google/protobuf/stubs/strutil.h>
+
+#include "perfetto/ext/base/string_utils.h"
 
 namespace perfetto {
 namespace ipc {
@@ -35,13 +35,13 @@
 using google::protobuf::FileDescriptor;
 using google::protobuf::MethodDescriptor;
 using google::protobuf::ServiceDescriptor;
-using google::protobuf::Split;
-using google::protobuf::StripString;
-using google::protobuf::StripSuffixString;
-using google::protobuf::UpperString;
 using google::protobuf::compiler::GeneratorContext;
 using google::protobuf::io::Printer;
 using google::protobuf::io::ZeroCopyOutputStream;
+using perfetto::base::SplitString;
+using perfetto::base::StripChars;
+using perfetto::base::StripSuffix;
+using perfetto::base::ToUpper;
 
 static const char kBanner[] = "// DO NOT EDIT. Autogenerated by Perfetto IPC\n";
 
@@ -113,7 +113,7 @@
 )";
 
 std::string StripName(const FileDescriptor& file) {
-  return StripSuffixString(file.name(), ".proto");
+  return StripSuffix(file.name(), ".proto");
 }
 
 std::string GetStubName(const FileDescriptor& file) {
@@ -138,7 +138,7 @@
                            const ServiceDescriptor& svc,
                            Printer* printer) {
   printer->Print("\n");
-  std::vector<std::string> namespaces = Split(file.package(), ".");
+  std::vector<std::string> namespaces = SplitString(file.package(), ".");
   for (const std::string& ns : namespaces)
     printer->Print("namespace $ns$ {\n", "ns", ns);
 
@@ -185,7 +185,7 @@
                         Printer* printer) {
   printer->Print("\n");
 
-  std::vector<std::string> namespaces = Split(file.package(), ".");
+  std::vector<std::string> namespaces = SplitString(file.package(), ".");
   for (const std::string& ns : namespaces)
     printer->Print("namespace $ns$ {\n", "ns", ns);
 
@@ -251,9 +251,8 @@
   Printer h_printer(h_fstream.get(), '$');
   Printer cc_printer(cc_fstream.get(), '$');
 
-  std::string guard = file->package() + "_" + file->name() + "_H_";
-  UpperString(&guard);
-  StripString(&guard, ".-/\\", '_');
+  std::string guard = ToUpper(file->package() + "_" + file->name() + "_H_");
+  guard = StripChars(guard, ".-/\\", '_');
 
   h_printer.Print(kBanner);
   h_printer.Print("#ifndef $guard$\n#define $guard$\n\n", "guard", guard);
diff --git a/src/ipc/service_proxy.cc b/src/ipc/service_proxy.cc
index 5edb857..e416f5f 100644
--- a/src/ipc/service_proxy.cc
+++ b/src/ipc/service_proxy.cc
@@ -18,7 +18,6 @@
 
 #include <utility>
 
-#include <google/protobuf/message_lite.h>
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/ipc/service_descriptor.h"
diff --git a/src/perfetto_cmd/pbtxt_to_pb.cc b/src/perfetto_cmd/pbtxt_to_pb.cc
index 038f1b8..101100d 100644
--- a/src/perfetto_cmd/pbtxt_to_pb.cc
+++ b/src/perfetto_cmd/pbtxt_to_pb.cc
@@ -21,8 +21,6 @@
 
 #include "src/perfetto_cmd/pbtxt_to_pb.h"
 
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/string_view.h"
@@ -41,8 +39,6 @@
 using protos::EnumValueDescriptorProto;
 using protos::FieldDescriptorProto;
 using protos::FileDescriptorSet;
-using ::google::protobuf::io::ZeroCopyInputStream;
-using ::google::protobuf::io::ArrayInputStream;
 
 namespace {
 
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 7bf529a..cdf6017 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -30,8 +30,6 @@
 #include <iterator>
 #include <sstream>
 
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
diff --git a/src/profiling/memory/CHANGELOG.md b/src/profiling/memory/CHANGELOG.md
index 199ceab..56f64b5 100644
--- a/src/profiling/memory/CHANGELOG.md
+++ b/src/profiling/memory/CHANGELOG.md
@@ -1,4 +1,4 @@
-# Changes from Android Q
+# Changes from Android 10
 
 ## New features
 * Allow to specify whether profiling should only be done for existing processes
@@ -9,3 +9,4 @@
 * Allow to dump the maximum, rather than at the time of the dump.
 
 ## Bugfixes
+* Fixed heapprofd on x86.
diff --git a/src/profiling/memory/bookkeeping.cc b/src/profiling/memory/bookkeeping.cc
index ae5ae71..bd6ee97 100644
--- a/src/profiling/memory/bookkeeping.cc
+++ b/src/profiling/memory/bookkeeping.cc
@@ -58,7 +58,7 @@
       alloc.sample_size = sample_size;
       alloc.alloc_size = alloc_size;
       alloc.sequence_number = sequence_number;
-      alloc.callstack_allocations = MaybeCreateCallstackAllocations(node);
+      alloc.SetCallstackAllocations(MaybeCreateCallstackAllocations(node));
     }
   } else {
     GlobalCallstackTrie::Node* node = callsites_->CreateCallsite(callstack);
diff --git a/src/profiling/memory/bookkeeping.h b/src/profiling/memory/bookkeeping.h
index 3cfeff9..c157e07 100644
--- a/src/profiling/memory/bookkeeping.h
+++ b/src/profiling/memory/bookkeeping.h
@@ -290,7 +290,7 @@
     for (const auto& addr_and_allocation : allocations_) {
       const Allocation& alloc = addr_and_allocation.second;
       fn(addr_and_allocation.first, alloc.sample_size, alloc.alloc_size,
-         alloc.callstack_allocations->node->id());
+         alloc.callstack_allocations()->node->id());
     }
   }
 
@@ -313,11 +313,8 @@
                uint64_t asize,
                uint64_t seq,
                CallstackAllocations* csa)
-        : sample_size(size),
-          alloc_size(asize),
-          sequence_number(seq),
-          callstack_allocations(csa) {
-      callstack_allocations->allocs++;
+        : sample_size(size), alloc_size(asize), sequence_number(seq) {
+      SetCallstackAllocations(csa);
     }
 
     Allocation() = default;
@@ -326,19 +323,30 @@
       sample_size = other.sample_size;
       alloc_size = other.alloc_size;
       sequence_number = other.sequence_number;
-      callstack_allocations = other.callstack_allocations;
-      other.callstack_allocations = nullptr;
+      callstack_allocations_ = other.callstack_allocations_;
+      other.callstack_allocations_ = nullptr;
     }
 
-    ~Allocation() {
-      if (callstack_allocations)
-        callstack_allocations->allocs--;
+    ~Allocation() { SetCallstackAllocations(nullptr); }
+
+    void SetCallstackAllocations(CallstackAllocations* callstack_allocations) {
+      if (callstack_allocations_)
+        callstack_allocations_->allocs--;
+      callstack_allocations_ = callstack_allocations;
+      if (callstack_allocations_)
+        callstack_allocations_->allocs++;
+    }
+
+    CallstackAllocations* callstack_allocations() const {
+      return callstack_allocations_;
     }
 
     uint64_t sample_size;
     uint64_t alloc_size;
     uint64_t sequence_number;
-    CallstackAllocations* callstack_allocations;
+
+   private:
+    CallstackAllocations* callstack_allocations_ = nullptr;
   };
 
   struct PendingOperation {
@@ -374,19 +382,19 @@
                        const PendingOperation& operation);
 
   void AddToCallstackAllocations(uint64_t ts, const Allocation& alloc) {
-    alloc.callstack_allocations->allocation_count++;
+    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 += alloc.sample_size;
 
       if (current_unfreed_ <= max_unfreed_)
         return;
 
       if (max_sequence_number_ == alloc.sequence_number - 1) {
-        alloc.callstack_allocations->value.retain_max.max =
+        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.cur;
       } else {
         for (auto& p : callstack_allocations_) {
           // We need to reset max = cur for every CallstackAllocation, as we
@@ -400,17 +408,18 @@
       max_unfreed_ = current_unfreed_;
       max_timestamp_ = ts;
     } else {
-      alloc.callstack_allocations->value.totals.allocated += alloc.sample_size;
+      alloc.callstack_allocations()->value.totals.allocated +=
+          alloc.sample_size;
     }
   }
 
   void SubtractFromCallstackAllocations(const Allocation& alloc) {
-    alloc.callstack_allocations->free_count++;
+    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 -= alloc.sample_size;
     } else {
-      alloc.callstack_allocations->value.totals.freed += alloc.sample_size;
+      alloc.callstack_allocations()->value.totals.freed += alloc.sample_size;
     }
   }
 
diff --git a/src/profiling/memory/bookkeeping_dump.cc b/src/profiling/memory/bookkeeping_dump.cc
index 0020a7a..13e24bc 100644
--- a/src/profiling/memory/bookkeeping_dump.cc
+++ b/src/profiling/memory/bookkeeping_dump.cc
@@ -169,7 +169,7 @@
   MakeProfilePacket();
 }
 
-void DumpState::AddIdleBytes(uintptr_t callstack_id, uint64_t bytes) {
+void DumpState::AddIdleBytes(uint64_t callstack_id, uint64_t bytes) {
   current_process_idle_allocs_[callstack_id] += bytes;
 }
 
diff --git a/src/profiling/memory/bookkeeping_dump.h b/src/profiling/memory/bookkeeping_dump.h
index 1daa86b..bb09914 100644
--- a/src/profiling/memory/bookkeeping_dump.h
+++ b/src/profiling/memory/bookkeeping_dump.h
@@ -69,7 +69,7 @@
   DumpState(DumpState&&) = delete;
   DumpState& operator=(DumpState&&) = delete;
 
-  void AddIdleBytes(uintptr_t callstack_id, uint64_t bytes);
+  void AddIdleBytes(uint64_t callstack_id, uint64_t bytes);
 
   void WriteAllocation(const HeapTracker::CallstackAllocations& alloc,
                        bool dump_at_max_mode);
@@ -122,7 +122,7 @@
       current_process_heap_samples_ = nullptr;
   std::function<void(protos::pbzero::ProfilePacket::ProcessHeapSamples*)>
       current_process_fill_header_;
-  std::map<uintptr_t /* callstack_id */, uint64_t> current_process_idle_allocs_;
+  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 064712c..940e23c 100644
--- a/src/profiling/memory/bookkeeping_unittest.cc
+++ b/src/profiling/memory/bookkeeping_unittest.cc
@@ -63,6 +63,20 @@
   hd.GetCallstackAllocations([](const HeapTracker::CallstackAllocations&) {});
 }
 
+TEST(BookkeepingTest, Replace) {
+  uint64_t sequence_number = 1;
+  GlobalCallstackTrie c;
+  HeapTracker hd(&c, false);
+
+  hd.RecordMalloc(stack(), 1, 5, 5, sequence_number, 100 * sequence_number);
+  sequence_number++;
+  hd.RecordMalloc(stack2(), 1, 2, 2, sequence_number, 100 * sequence_number);
+
+  // Call GetCallstackAllocations twice to force GC of old CallstackAllocations.
+  hd.GetCallstackAllocations([](const HeapTracker::CallstackAllocations&) {});
+  hd.GetCallstackAllocations([](const HeapTracker::CallstackAllocations&) {});
+}
+
 TEST(BookkeepingTest, Basic) {
   uint64_t sequence_number = 1;
   GlobalCallstackTrie c;
diff --git a/src/profiling/memory/ext.h b/src/profiling/memory/ext.h
new file mode 100644
index 0000000..b353e98
--- /dev/null
+++ b/src/profiling/memory/ext.h
@@ -0,0 +1,66 @@
+/*
+ * 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_EXT_H_
+#define SRC_PROFILING_MEMORY_EXT_H_
+
+// Header only code that gets used in other projects.
+// This is currently used in
+// * ART
+// * Bionic
+// * Heapprofd
+//
+// DO NOT USE THE STL HERE. This gets used in parts of Bionic that do not
+// use the STL.
+
+#include <string.h>
+
+namespace perfetto {
+namespace profiling {
+
+// Normalize cmdline in place. Stores new beginning of string in *cmdline_ptr.
+// Returns new size of string (from new beginning).
+// Modifies string in *cmdline_ptr.
+static ssize_t NormalizeCmdLine(char** cmdline_ptr, size_t size) {
+  char* cmdline = *cmdline_ptr;
+  char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
+  if (first_arg == nullptr) {
+    errno = EOVERFLOW;
+    return -1;
+  }
+  // For consistency with what we do with Java app cmdlines, trim everything
+  // after the @ sign of the first arg.
+  char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
+  if (first_at != nullptr && first_at < first_arg) {
+    *first_at = '\0';
+    first_arg = first_at;
+  }
+  char* start = static_cast<char*>(
+      memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
+  if (start == nullptr) {
+    start = cmdline;
+  } else {
+    // Skip the /.
+    start++;
+  }
+  *cmdline_ptr = start;
+  return first_arg - start;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_EXT_H_
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index fb3b254..42e666f 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -738,8 +738,8 @@
 }
 
 TEST_P(HeapprofdEndToEnd, ReInit) {
-  constexpr uint64_t kFirstIterationBytes = 5;
-  constexpr uint64_t kSecondIterationBytes = 7;
+  constexpr size_t kFirstIterationBytes = 5;
+  constexpr size_t kSecondIterationBytes = 7;
 
   base::Pipe signal_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
   base::Pipe ack_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
@@ -750,7 +750,7 @@
     case -1:
       PERFETTO_FATAL("Failed to fork.");
     case 0: {
-      uint64_t bytes = kFirstIterationBytes;
+      size_t bytes = kFirstIterationBytes;
       signal_pipe.wr.reset();
       ack_pipe.rd.reset();
       for (;;) {
@@ -819,7 +819,7 @@
   PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
 }
 
-TEST_P(HeapprofdEndToEnd, DISABLED_ConcurrentSession) {
+TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
   constexpr size_t kAllocSize = 1024;
 
   pid_t pid = ForkContinuousMalloc(kAllocSize);
@@ -961,10 +961,8 @@
 
 // This test only works when run on Android using an Android Q version of
 // Bionic.
-// TODO(b/118428762): look into unwinding issues on x86.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||                        \
-    PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS) || defined(__i386__) || \
-    defined(__x86_64__)
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
 INSTANTIATE_TEST_CASE_P(DISABLED_Run, HeapprofdEndToEnd, Bool(), TestSuffix);
 #else
 INSTANTIATE_TEST_CASE_P(Run, HeapprofdEndToEnd, Bool(), TestSuffix);
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 155dd77..c9c7767 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -577,18 +577,19 @@
 
   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,
-                                          uintptr_t callstack_id) {
-          int64_t idle = page_idle_checker.OnIdlePage(addr, alloc_size);
-          if (idle < 0) {
-            PERFETTO_PLOG("OnIdlePage.");
-            return;
-          }
-          if (idle > 0)
-            dump_state.AddIdleBytes(callstack_id, static_cast<uint64_t>(idle));
-        });
+    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(
@@ -810,7 +811,7 @@
   if (shmem_size > kMaxShmemSize)
     shmem_size = kMaxShmemSize;
 
-  auto shmem = SharedRingBuffer::Create(shmem_size);
+  auto shmem = SharedRingBuffer::Create(static_cast<size_t>(shmem_size));
   if (!shmem || !shmem->is_valid()) {
     PERFETTO_LOG("Failed to create shared memory.");
     return;
diff --git a/src/profiling/memory/page_idle_checker.cc b/src/profiling/memory/page_idle_checker.cc
index e0f0ef3..7e639b5 100644
--- a/src/profiling/memory/page_idle_checker.cc
+++ b/src/profiling/memory/page_idle_checker.cc
@@ -34,7 +34,7 @@
   if ((addr + size) % base::kPageSize != 0)
     end_page_nr++;
 
-  size_t pages = end_page_nr - 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) {
diff --git a/src/profiling/memory/proc_utils.cc b/src/profiling/memory/proc_utils.cc
index ba5ee79..07f85cd 100644
--- a/src/profiling/memory/proc_utils.cc
+++ b/src/profiling/memory/proc_utils.cc
@@ -21,28 +21,12 @@
 #include <unistd.h>
 
 #include "perfetto/ext/base/file_utils.h"
+#include "src/profiling/memory/ext.h"
 
 namespace perfetto {
 namespace profiling {
 namespace {
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
-
-const char* FindChar(const char* s, char c, size_t n) {
-  std::string str(s, n);
-  auto idx = str.rfind(c);
-  if (idx == std::string::npos)
-    return nullptr;
-  return s + n;
-}
-
-void* memrchr(const void* s, int c, size_t n) {
-  return static_cast<void*>(const_cast<char*>(
-      FindChar(static_cast<const char*>(s), static_cast<char>(c), n)));
-}
-
-#endif
-
 bool GetProcFile(pid_t pid, const char* file, char* filename_buf, size_t size) {
   ssize_t written = snprintf(filename_buf, size, "/proc/%d/%s", pid, file);
   if (written < 0 || static_cast<size_t>(written) >= size) {
@@ -57,38 +41,6 @@
 
 }  // namespace
 
-bool NormalizeCmdLine(char* cmdline, size_t size, std::string* name) {
-  char* first_arg = static_cast<char*>(memchr(cmdline, '\0', size));
-  if (first_arg == nullptr) {
-    PERFETTO_DLOG("Overflow reading cmdline");
-    errno = EOVERFLOW;
-    return false;
-  }
-  // For consistency with what we do with Java app cmdlines, trim everything
-  // after the @ sign of the first arg.
-  char* first_at = static_cast<char*>(memchr(cmdline, '@', size));
-  if (first_at != nullptr && first_at < first_arg) {
-    *first_at = '\0';
-    first_arg = first_at;
-  }
-  char* start = static_cast<char*>(
-      memrchr(cmdline, '/', static_cast<size_t>(first_arg - cmdline)));
-  if (start == first_arg) {
-    // The first argument ended in a slash.
-    PERFETTO_DLOG("cmdline ends in /");
-    errno = EINVAL;
-    return false;
-  } else if (start == nullptr) {
-    start = cmdline;
-  } else {
-    // Skip the /.
-    start++;
-  }
-  size_t name_size = static_cast<size_t>(first_arg - start);
-  name->assign(start, name_size);
-  return true;
-}
-
 std::vector<std::string> NormalizeCmdlines(
     const std::vector<std::string>& cmdlines) {
   std::vector<std::string> normalized_cmdlines;
@@ -96,12 +48,15 @@
     // Add nullbyte to make sure it's a C string.
     cmdline.resize(cmdline.size() + 1, '\0');
     std::string normalized;
-    if (!NormalizeCmdLine(&(cmdline[0]), cmdline.size(), &normalized)) {
-      PERFETTO_ELOG("Failed to normalize cmdline %s. Skipping.",
+    char* cmdline_cstr = &(cmdline[0]);
+    ssize_t size = NormalizeCmdLine(&cmdline_cstr, cmdline.size());
+    if (size == -1) {
+      PERFETTO_PLOG("Failed to normalize cmdline %s. Skipping.",
                     cmdline.c_str());
       continue;
     }
-    normalized_cmdlines.emplace_back(std::move(normalized));
+    normalized_cmdlines.emplace_back(
+        std::string(cmdline_cstr, static_cast<size_t>(size)));
   }
   return normalized_cmdlines;
 }
@@ -138,7 +93,12 @@
   }
 
   cmdline[rd] = '\0';
-  return NormalizeCmdLine(cmdline, static_cast<size_t>(rd), name);
+  char* cmdline_start = cmdline;
+  ssize_t size = NormalizeCmdLine(&cmdline_start, static_cast<size_t>(rd));
+  if (size == -1)
+    return false;
+  name->assign(cmdline_start, static_cast<size_t>(size));
+  return true;
 }
 
 void FindAllProfilablePids(std::set<pid_t>* pids) {
diff --git a/src/profiling/memory/proc_utils.h b/src/profiling/memory/proc_utils.h
index d20d8ba..e123194 100644
--- a/src/profiling/memory/proc_utils.h
+++ b/src/profiling/memory/proc_utils.h
@@ -43,7 +43,6 @@
   }
 }
 
-bool NormalizeCmdLine(char* cmdline, size_t size, std::string* name);
 std::vector<std::string> NormalizeCmdlines(
     const std::vector<std::string>& cmdlines);
 
diff --git a/src/profiling/memory/proc_utils_unittest.cc b/src/profiling/memory/proc_utils_unittest.cc
index b011e4f..b1881bb 100644
--- a/src/profiling/memory/proc_utils_unittest.cc
+++ b/src/profiling/memory/proc_utils_unittest.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/profiling/memory/proc_utils.h"
+#include "src/profiling/memory/ext.h"
 
 #include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
@@ -26,25 +27,77 @@
 using ::testing::Contains;
 using ::testing::Not;
 
+std::string NormalizeToString(char* cmdline, size_t size) {
+  ssize_t new_size = NormalizeCmdLine(&cmdline, size);
+  if (new_size == -1)
+    return "";
+  return std::string(cmdline, static_cast<size_t>(new_size));
+}
+
 TEST(ProcUtilsTest, NormalizeNoop) {
-  char kCmdline[] = "surfaceflinger\0";
-  std::string name;
-  ASSERT_TRUE(NormalizeCmdLine(kCmdline, sizeof(kCmdline), &name));
-  EXPECT_EQ(name, "surfaceflinger");
+  char kCmdline[] = "surfaceflinger";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "surfaceflinger");
+}
+
+TEST(ProcUtilsTest, NormalizeTwoArgs) {
+  char kCmdline[] = "surfaceflinger\0--foo";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "surfaceflinger");
 }
 
 TEST(ProcUtilsTest, NormalizePath) {
-  char kCmdline[] = "/system/bin/surfaceflinger\0";
-  std::string name;
-  ASSERT_TRUE(NormalizeCmdLine(kCmdline, sizeof(kCmdline), &name));
-  EXPECT_EQ(name, "surfaceflinger");
+  char kCmdline[] = "/system/bin/surfaceflinger";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "surfaceflinger");
 }
 
 TEST(ProcUtilsTest, NormalizeAt) {
-  char kCmdline[] = "some.app@2.0\0";
-  std::string name;
-  ASSERT_TRUE(NormalizeCmdLine(kCmdline, sizeof(kCmdline), &name));
-  EXPECT_EQ(name, "some.app");
+  char kCmdline[] = "some.app@2.0";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "some.app");
+}
+
+TEST(ProcUtilsTest, NormalizeEmpty) {
+  char kCmdline[] = "";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeTrailingAt) {
+  char kCmdline[] = "foo@";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "foo");
+}
+
+TEST(ProcUtilsTest, NormalizeOnlyTrailingAt) {
+  char kCmdline[] = "@";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeTrailingSlash) {
+  char kCmdline[] = "foo/";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeOnlySlash) {
+  char kCmdline[] = "/";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeTwoArgsSlash) {
+  char kCmdline[] = "surfaceflinger/\0--foo";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeEmptyFirstArg) {
+  char kCmdline[] = "\0--foo";
+  EXPECT_EQ(NormalizeToString(kCmdline, sizeof(kCmdline)), "");
+}
+
+TEST(ProcUtilsTest, NormalizeNoNullTerminated) {
+  char kCmdline[] = {'f'};
+  char* cmdline = kCmdline;
+  EXPECT_EQ(NormalizeCmdLine(&cmdline, sizeof(kCmdline)), -1);
+}
+
+TEST(ProcUtilsTest, NormalizeZeroLength) {
+  char* cmdline = nullptr;
+  EXPECT_EQ(NormalizeCmdLine(&cmdline, 0), -1);
 }
 
 TEST(ProcUtilsTest, FindProfilablePids) {
diff --git a/src/profiling/memory/sampler.h b/src/profiling/memory/sampler.h
index b46d4bf..6740d17 100644
--- a/src/profiling/memory/sampler.h
+++ b/src/profiling/memory/sampler.h
@@ -54,7 +54,7 @@
   size_t SampleSize(size_t alloc_sz) {
     if (PERFETTO_UNLIKELY(alloc_sz >= sampling_interval_))
       return alloc_sz;
-    return sampling_interval_ * NumberOfSamples(alloc_sz);
+    return static_cast<size_t>(sampling_interval_ * NumberOfSamples(alloc_sz));
   }
 
  private:
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 91215ed..59a4dca 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -72,38 +72,49 @@
 static std::vector<std::string> kSkipMaps{"heapprofd_client.so"};
 #pragma GCC diagnostic pop
 
-std::unique_ptr<unwindstack::Regs> CreateFromRawData(unwindstack::ArchEnum arch,
-                                                     void* raw_data) {
-  std::unique_ptr<unwindstack::Regs> ret;
-  // unwindstack::RegsX::Read returns a raw ptr which we are expected to free.
-  switch (arch) {
-    case unwindstack::ARCH_X86:
-      ret.reset(unwindstack::RegsX86::Read(raw_data));
-      break;
-    case unwindstack::ARCH_X86_64:
-      ret.reset(unwindstack::RegsX86_64::Read(raw_data));
-      break;
-    case unwindstack::ARCH_ARM:
-      ret.reset(unwindstack::RegsArm::Read(raw_data));
-      break;
-    case unwindstack::ARCH_ARM64:
-      ret.reset(unwindstack::RegsArm64::Read(raw_data));
-      break;
-    case unwindstack::ARCH_MIPS:
-      ret.reset(unwindstack::RegsMips::Read(raw_data));
-      break;
-    case unwindstack::ARCH_MIPS64:
-      ret.reset(unwindstack::RegsMips64::Read(raw_data));
-      break;
-    case unwindstack::ARCH_UNKNOWN:
-      ret.reset(nullptr);
-      break;
-  }
-  return ret;
+size_t GetRegsSize(unwindstack::Regs* regs) {
+  if (regs->Is32Bit())
+    return sizeof(uint32_t) * regs->total_regs();
+  return sizeof(uint64_t) * regs->total_regs();
+}
+
+void ReadFromRawData(unwindstack::Regs* regs, void* raw_data) {
+  memcpy(regs->RawData(), raw_data, GetRegsSize(regs));
 }
 
 }  // namespace
 
+std::unique_ptr<unwindstack::Regs> CreateRegsFromRawData(
+    unwindstack::ArchEnum arch,
+    void* raw_data) {
+  std::unique_ptr<unwindstack::Regs> ret;
+  switch (arch) {
+    case unwindstack::ARCH_X86:
+      ret.reset(new unwindstack::RegsX86());
+      break;
+    case unwindstack::ARCH_X86_64:
+      ret.reset(new unwindstack::RegsX86_64());
+      break;
+    case unwindstack::ARCH_ARM:
+      ret.reset(new unwindstack::RegsArm());
+      break;
+    case unwindstack::ARCH_ARM64:
+      ret.reset(new unwindstack::RegsArm64());
+      break;
+    case unwindstack::ARCH_MIPS:
+      ret.reset(new unwindstack::RegsMips());
+      break;
+    case unwindstack::ARCH_MIPS64:
+      ret.reset(new unwindstack::RegsMips64());
+      break;
+    case unwindstack::ARCH_UNKNOWN:
+      break;
+  }
+  if (ret)
+    ReadFromRawData(ret.get(), raw_data);
+  return ret;
+}
+
 StackOverlayMemory::StackOverlayMemory(std::shared_ptr<unwindstack::Memory> mem,
                                        uint64_t sp,
                                        uint8_t* stack,
@@ -165,8 +176,8 @@
 
 bool DoUnwind(WireMessage* msg, UnwindingMetadata* metadata, AllocRecord* out) {
   AllocMetadata* alloc_metadata = msg->alloc_header;
-  std::unique_ptr<unwindstack::Regs> regs(
-      CreateFromRawData(alloc_metadata->arch, alloc_metadata->register_data));
+  std::unique_ptr<unwindstack::Regs> regs(CreateRegsFromRawData(
+      alloc_metadata->arch, alloc_metadata->register_data));
   if (regs == nullptr) {
     PERFETTO_DLOG("Unable to construct unwindstack::Regs");
     unwindstack::FrameData frame_data{};
@@ -195,6 +206,9 @@
     if (attempt > 0) {
       PERFETTO_DLOG("Reparsing maps");
       metadata->ReparseMaps();
+      // Regs got invalidated by libuwindstack's speculative jump.
+      // Reset.
+      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());
diff --git a/src/profiling/memory/unwinding.h b/src/profiling/memory/unwinding.h
index 9fab671..39216fe 100644
--- a/src/profiling/memory/unwinding.h
+++ b/src/profiling/memory/unwinding.h
@@ -37,6 +37,10 @@
 namespace perfetto {
 namespace profiling {
 
+std::unique_ptr<unwindstack::Regs> CreateRegsFromRawData(
+    unwindstack::ArchEnum arch,
+    void* raw_data);
+
 // Read /proc/[pid]/maps from an open file descriptor.
 // TODO(fmayer): Figure out deduplication to other maps.
 class FileDescriptorMaps : public unwindstack::Maps {
@@ -106,7 +110,8 @@
             new unwindstack::DexFiles(fd_mem)))
 #endif
   {
-    PERFETTO_DCHECK(maps.Parse());
+    bool parsed = maps.Parse();
+    PERFETTO_DCHECK(parsed);
   }
   void ReparseMaps() {
     reparses++;
diff --git a/src/profiling/memory/unwinding_unittest.cc b/src/profiling/memory/unwinding_unittest.cc
index a3c45db..b52a7ce 100644
--- a/src/profiling/memory/unwinding_unittest.cc
+++ b/src/profiling/memory/unwinding_unittest.cc
@@ -68,11 +68,27 @@
   ASSERT_EQ(map_info->name, "[stack]");
 }
 
+void __attribute__((noinline)) AssertFunctionOffset() {
+  constexpr auto kMaxFunctionSize = 1000u;
+  // Need to zero-initialize to make MSAN happy. MSAN does not see the writes
+  // from AsmGetRegs (as it is in assembly) and complains otherwise.
+  char reg_data[kMaxRegisterDataSize] = {};
+  unwindstack::AsmGetRegs(reg_data);
+  auto regs = CreateRegsFromRawData(unwindstack::Regs::CurrentArch(), reg_data);
+  ASSERT_GT(regs->pc(), reinterpret_cast<uint64_t>(&AssertFunctionOffset));
+  ASSERT_LT(regs->pc() - reinterpret_cast<uint64_t>(&AssertFunctionOffset),
+            kMaxFunctionSize);
+}
+
+TEST(UnwindingTest, TestFunctionOffset) {
+  AssertFunctionOffset();
+}
+
 // This is needed because ASAN thinks copying the whole stack is a buffer
 // underrun.
 void __attribute__((noinline))
 UnsafeMemcpy(void* dst, const void* src, size_t n)
-    __attribute__((no_sanitize("address", "hwaddress"))) {
+    __attribute__((no_sanitize("address", "hwaddress", "memory"))) {
   const uint8_t* from = reinterpret_cast<const uint8_t*>(src);
   uint8_t* to = reinterpret_cast<uint8_t*>(dst);
   for (size_t i = 0; i < n; ++i)
@@ -90,13 +106,16 @@
 
   const char* stackbase = GetThreadStackBase();
   const char* stacktop = 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.");
     return {nullptr, nullptr};
   }
-  uint64_t stack_size = static_cast<uint64_t>(stackbase - stacktop);
+  size_t stack_size = static_cast<size_t>(stackbase - stacktop);
 
   metadata->alloc_size = 10;
   metadata->alloc_address = 0x10;
@@ -115,14 +134,7 @@
   return {std::move(payload), std::move(metadata)};
 }
 
-// TODO(rsavitski): Investigate TSAN unwinding.
-#if defined(THREAD_SANITIZER)
-#define MAYBE_DoUnwind DISABLED_DoUnwind
-#else
-#define MAYBE_DoUnwind DoUnwind
-#endif
-
-TEST(UnwindingTest, MAYBE_DoUnwind) {
+TEST(UnwindingTest, DoUnwind) {
   base::ScopedFile proc_maps(base::OpenFile("/proc/self/maps", O_RDONLY));
   base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
   GlobalCallstackTrie callsites;
@@ -132,10 +144,35 @@
   auto record = GetRecord(&msg);
   AllocRecord out;
   ASSERT_TRUE(DoUnwind(&msg, &metadata, &out));
+  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));
-  ASSERT_EQ(st, 0);
+  ASSERT_EQ(st, 0) << "mangled: " << demangled.get()
+                   << ", frames: " << out.frames.size();
+  ASSERT_STREQ(demangled.get(),
+               "perfetto::profiling::(anonymous "
+               "namespace)::GetRecord(perfetto::profiling::WireMessage*)");
+}
+
+TEST(UnwindingTest, DoUnwindReparse) {
+  base::ScopedFile proc_maps(base::OpenFile("/proc/self/maps", O_RDONLY));
+  base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
+  GlobalCallstackTrie callsites;
+  UnwindingMetadata metadata(getpid(), std::move(proc_maps),
+                             std::move(proc_mem));
+  // Force reparse in DoUnwind.
+  metadata.maps.Reset();
+  WireMessage msg;
+  auto record = GetRecord(&msg);
+  AllocRecord out;
+  ASSERT_TRUE(DoUnwind(&msg, &metadata, &out));
+  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));
+  ASSERT_EQ(st, 0) << "mangled: " << demangled.get()
+                   << ", frames: " << out.frames.size();
   ASSERT_STREQ(demangled.get(),
                "perfetto::profiling::(anonymous "
                "namespace)::GetRecord(perfetto::profiling::WireMessage*)");
diff --git a/src/profiling/memory/wire_protocol.cc b/src/profiling/memory/wire_protocol.cc
index 6209fc8..6d561c3 100644
--- a/src/profiling/memory/wire_protocol.cc
+++ b/src/profiling/memory/wire_protocol.cc
@@ -87,7 +87,7 @@
       errno = EAGAIN;
       return false;
     }
-    buf = shmem->BeginWrite(lock, total_size);
+    buf = shmem->BeginWrite(lock, static_cast<size_t>(total_size));
   }
   if (!buf) {
     PERFETTO_DLOG("Buffer overflow.");
diff --git a/src/profiling/memory/wire_protocol.h b/src/profiling/memory/wire_protocol.h
index 898d2e7..3ca21d9 100644
--- a/src/profiling/memory/wire_protocol.h
+++ b/src/profiling/memory/wire_protocol.h
@@ -22,12 +22,12 @@
 
 #include <inttypes.h>
 #include <unwindstack/Elf.h>
-#include <unwindstack/UserArm.h>
-#include <unwindstack/UserArm64.h>
-#include <unwindstack/UserMips.h>
-#include <unwindstack/UserMips64.h>
-#include <unwindstack/UserX86.h>
-#include <unwindstack/UserX86_64.h>
+#include <unwindstack/MachineArm.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/MachineMips.h>
+#include <unwindstack/MachineMips64.h>
+#include <unwindstack/MachineX86.h>
+#include <unwindstack/MachineX86_64.h>
 
 #include "src/profiling/memory/shared_ring_buffer.h"
 
@@ -69,12 +69,12 @@
       constexpr_max(
         constexpr_max(
             constexpr_max(
-              sizeof(unwindstack::arm_user_regs),
-              sizeof(unwindstack::arm64_user_regs)),
-            sizeof(unwindstack::x86_user_regs)),
-          sizeof(unwindstack::x86_64_user_regs)),
-        sizeof(unwindstack::mips_user_regs)),
-      sizeof(unwindstack::mips64_user_regs)
+              sizeof(uint32_t) * unwindstack::ARM_REG_LAST,
+              sizeof(uint64_t) * unwindstack::ARM64_REG_LAST),
+            sizeof(uint32_t) * unwindstack::X86_REG_LAST),
+          sizeof(uint64_t) * unwindstack::X86_64_REG_LAST),
+        sizeof(uint32_t) * unwindstack::MIPS_REG_LAST),
+      sizeof(uint64_t) * unwindstack::MIPS64_REG_LAST
   );
 // clang-format on
 
diff --git a/src/protozero/protoc_plugin/BUILD.gn b/src/protozero/protoc_plugin/BUILD.gn
index 9cec6b8..432abd8 100644
--- a/src/protozero/protoc_plugin/BUILD.gn
+++ b/src/protozero/protoc_plugin/BUILD.gn
@@ -21,5 +21,6 @@
   deps = [
     "../../../gn:default_deps",
     "../../../gn:protoc_lib",
+    "../../../src/base",
   ]
 }
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index 08d985f..c1285bd 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <limits>
 #include <map>
 #include <memory>
 #include <set>
@@ -24,7 +25,8 @@
 #include <google/protobuf/descriptor.h>
 #include <google/protobuf/io/printer.h>
 #include <google/protobuf/io/zero_copy_stream.h>
-#include <google/protobuf/stubs/strutil.h>
+
+#include "perfetto/ext/base/string_utils.h"
 
 namespace protozero {
 namespace {
@@ -34,14 +36,15 @@
 using google::protobuf::EnumValueDescriptor;
 using google::protobuf::FieldDescriptor;
 using google::protobuf::FileDescriptor;
-using google::protobuf::Split;
-using google::protobuf::StripPrefixString;
-using google::protobuf::StripString;
-using google::protobuf::StripSuffixString;
-using google::protobuf::UpperString;
 using google::protobuf::compiler::GeneratorContext;
 using google::protobuf::io::Printer;
 using google::protobuf::io::ZeroCopyOutputStream;
+using perfetto::base::SplitString;
+using perfetto::base::StripChars;
+using perfetto::base::StripPrefix;
+using perfetto::base::StripSuffix;
+using perfetto::base::ToUpper;
+using perfetto::base::Uppercase;
 
 // Keep this value in sync with ProtoDecoder::kMaxDecoderFieldId. If they go out
 // of sync pbzero.h files will stop compiling, hitting the at() static_assert.
@@ -78,7 +81,7 @@
 };
 
 inline std::string ProtoStubName(const FileDescriptor* proto) {
-  return StripSuffixString(proto->name(), ".proto") + ".pbzero";
+  return StripSuffix(proto->name(), ".proto") + ".pbzero";
 }
 
 class GeneratorJob {
@@ -120,7 +123,7 @@
   template <class T>
   inline std::string GetDescriptorName(const T* descriptor) {
     if (!package_.empty()) {
-      return StripPrefixString(descriptor->full_name(), package_ + ".");
+      return StripPrefix(descriptor->full_name(), package_ + ".");
     } else {
       return descriptor->full_name();
     }
@@ -131,8 +134,7 @@
   // prohibited but not recommended in order to avoid name collisions.
   template <class T>
   inline std::string GetCppClassName(const T* descriptor, bool full = false) {
-    std::string name = GetDescriptorName(descriptor);
-    StripString(&name, ".", '_');
+    std::string name = StripChars(GetDescriptorName(descriptor), ".", '_');
     if (full)
       name = full_namespace_prefix_ + name;
     return name;
@@ -141,7 +143,7 @@
   inline std::string GetFieldNumberConstant(const FieldDescriptor* field) {
     std::string name = field->camelcase_name();
     if (!name.empty()) {
-      name.at(0) = static_cast<char>(toupper(name.at(0)));
+      name.at(0) = Uppercase(name.at(0));
       name = "k" + name + "FieldNumber";
     } else {
       // Protoc allows fields like 'bool _ = 1'.
@@ -322,7 +324,7 @@
   void Preprocess() {
     // Package name maps to a series of namespaces.
     package_ = source_->package();
-    namespaces_ = Split(package_, ".");
+    namespaces_ = SplitString(package_, ".");
     if (!wrapper_namespace_.empty())
       namespaces_.push_back(wrapper_namespace_);
 
@@ -339,8 +341,8 @@
     std::string greeting =
         "// Autogenerated by the ProtoZero compiler plugin. DO NOT EDIT.\n";
     std::string guard = package_ + "_" + source_->name() + "_H_";
-    UpperString(&guard);
-    StripString(&guard, ".-/\\", '_');
+    guard = ToUpper(guard);
+    guard = StripChars(guard, ".-/\\", '_');
 
     stub_h_->Print(
         "$greeting$\n"
@@ -827,8 +829,8 @@
   stub_cc_printer.Print("// Intentionally empty (crbug.com/998165)\n");
 
   // Parse additional options.
-  for (const std::string& option : Split(options, ",")) {
-    std::vector<std::string> option_pair = Split(option, "=");
+  for (const std::string& option : SplitString(options, ",")) {
+    std::vector<std::string> option_pair = SplitString(option, "=");
     job.SetOption(option_pair[0], option_pair[1]);
   }
 
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 78a6142..952dd65 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("../../gn/fuzzer.gni")
 import("../../gn/perfetto.gni")
+import("../../gn/perfetto_host_executable.gni")
 import("../../gn/test.gni")
 import("../../gn/wasm.gni")
 
@@ -158,8 +159,6 @@
     "trace_sorter.h",
     "trace_storage.cc",
     "trace_storage.h",
-    "track_table.cc",
-    "track_table.h",
     "variadic.h",
     "virtual_destructors.cc",
     "virtual_track_tracker.cc",
@@ -230,12 +229,11 @@
   ]
 }
 
-executable("trace_processor_shell") {
-  testonly = true  # We need this for proto full.
+perfetto_host_executable("trace_processor_shell") {
   deps = [
     ":lib",
     "../../gn:default_deps",
-    "../../gn:protobuf_full",
+    "../../gn:protoc_lib",
     "../base",
     "metrics:lib",
   ]
diff --git a/src/trace_processor/db/column.cc b/src/trace_processor/db/column.cc
index 7fe09bf..1f77d3b 100644
--- a/src/trace_processor/db/column.cc
+++ b/src/trace_processor/db/column.cc
@@ -21,17 +21,34 @@
 namespace perfetto {
 namespace trace_processor {
 
+Column::Column(const Column& column,
+               Table* table,
+               uint32_t col_idx,
+               uint32_t row_map_idx)
+    : Column(column.name_,
+             column.type_,
+             table,
+             col_idx,
+             row_map_idx,
+             column.sparse_vector_) {}
+
 Column::Column(const char* name,
                ColumnType type,
                Table* table,
                uint32_t col_idx,
-               uint32_t row_map_idx)
-    : string_pool_(table->string_pool_),
-      type_(type),
+               uint32_t row_map_idx,
+               void* sparse_vector)
+    : type_(type),
+      sparse_vector_(sparse_vector),
       name_(name),
       table_(table),
       col_idx_(col_idx),
-      row_map_idx_(row_map_idx) {}
+      row_map_idx_(row_map_idx),
+      string_pool_(table->string_pool_) {}
+
+Column Column::IdColumn(Table* table, uint32_t col_idx, uint32_t row_map_idx) {
+  return Column("id", ColumnType::kId, table, col_idx, row_map_idx, nullptr);
+}
 
 void Column::FilterInto(FilterOp op, SqlValue value, RowMap* iv) const {
   // Assume op == kEq.
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 12ff23c..becab4e 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -59,62 +59,48 @@
 // Represents a named, strongly typed list of data.
 class Column {
  public:
-  // Create a nullable uint32 Column.
-  // Note: |name| must be a long lived string.
+  template <typename T>
   Column(const char* name,
-         const SparseVector<uint32_t>* storage,
+         SparseVector<T>* storage,
          Table* table,
          uint32_t col_idx,
          uint32_t row_map_idx)
-      : Column(name, ColumnType::kUint32, table, col_idx, row_map_idx) {
-    data_.uint32_sv = storage;
-  }
-
-  // Create a nullable int64 Column.
-  // Note: |name| must be a long lived string.
-  Column(const char* name,
-         const SparseVector<int64_t>* storage,
-         Table* table,
-         uint32_t col_idx,
-         uint32_t row_map_idx)
-      : Column(name, ColumnType::kInt64, table, col_idx, row_map_idx) {
-    data_.int64_sv = storage;
-  }
-
-  // Create an nullable string Column.
-  // Note: |name| must be a long lived string.
-  // TODO(lalitm): investigate changing this to a std::deque instead as
-  // StringIds already have the concept of nullability in them.
-  Column(const char* name,
-         const SparseVector<StringPool::Id>* storage,
-         Table* table,
-         uint32_t col_idx,
-         uint32_t row_map_idx)
-      : Column(name, ColumnType::kString, table, col_idx, row_map_idx) {
-    data_.string_sv = storage;
-  }
+      : Column(name, ToColumnType<T>(), table, col_idx, row_map_idx, storage) {}
 
   // Create a Column has the same name and is backed by the same data as
   // |column| but is associated to a different table.
   Column(const Column& column,
          Table* table,
          uint32_t col_idx,
-         uint32_t row_map_idx)
-      : Column(column.name_, column.type_, table, col_idx, row_map_idx) {
-    data_ = column.data_;
-  }
+         uint32_t row_map_idx);
 
+  // Columns are movable but not copyable.
   Column(Column&&) noexcept = default;
   Column& operator=(Column&&) = default;
 
   // Creates a Column which returns the index as the value of the row.
-  static Column IdColumn(Table* table, uint32_t col_idx, uint32_t row_map_idx) {
-    return Column("id", ColumnType::kId, table, col_idx, row_map_idx);
-  }
+  static Column IdColumn(Table* table, uint32_t col_idx, uint32_t row_map_idx);
 
   // Gets the value of the Column at the given |row|.
-  // See the bottom of this header for the definition of this function.
-  SqlValue Get(uint32_t row) const;
+  SqlValue Get(uint32_t row) const {
+    switch (type_) {
+      case ColumnType::kUint32: {
+        auto opt_value = GetTyped<uint32_t>(row);
+        return opt_value ? SqlValue::Long(*opt_value) : SqlValue();
+      }
+      case ColumnType::kInt64: {
+        auto opt_value = GetTyped<int64_t>(row);
+        return opt_value ? SqlValue::Long(*opt_value) : SqlValue();
+      }
+      case ColumnType::kString: {
+        auto str = GetStringPoolString(row).c_str();
+        return str == nullptr ? SqlValue() : SqlValue::String(str);
+      }
+      case ColumnType::kId:
+        return SqlValue::Long(row_map().Get(row));
+    }
+    PERFETTO_FATAL("For GCC");
+  }
 
   // Returns the row containing the given value in the Column.
   base::Optional<uint32_t> IndexOf(SqlValue value) const {
@@ -144,6 +130,20 @@
   // given filter constraint.
   void FilterInto(FilterOp, SqlValue value, RowMap*) const;
 
+  const RowMap& row_map() const;
+  const char* name() const { return name_; }
+  SqlValue::Type type() const {
+    switch (type_) {
+      case ColumnType::kUint32:
+      case ColumnType::kInt64:
+      case ColumnType::kId:
+        return SqlValue::Type::kLong;
+      case ColumnType::kString:
+        return SqlValue::Type::kString;
+    }
+    PERFETTO_FATAL("For GCC");
+  }
+
   // Returns a Constraint for each type of filter operation for this Column.
   Constraint eq(SqlValue value) const {
     return Constraint{col_idx_, FilterOp::kEq, value};
@@ -162,21 +162,6 @@
   // Returns the JoinKey for this Column.
   JoinKey join_key() const { return JoinKey{col_idx_}; }
 
-  const RowMap& row_map() const;
-  const char* name() const { return name_; }
-
-  SqlValue::Type type() const {
-    switch (type_) {
-      case ColumnType::kUint32:
-      case ColumnType::kInt64:
-      case ColumnType::kId:
-        return SqlValue::Type::kLong;
-      case ColumnType::kString:
-        return SqlValue::Type::kString;
-    }
-    PERFETTO_FATAL("For GCC");
-  }
-
  protected:
   enum ColumnType {
     // Standard primitive types.
@@ -188,25 +173,27 @@
     kId,
   };
 
-  // See the bottom of this header file for the explicit instantiations of these
-  // function.
   template <typename T>
-  base::Optional<T> GetTyped(uint32_t row) const;
-  NullTermStringView GetString(uint32_t row) const;
+  base::Optional<T> GetTyped(uint32_t row) const {
+    PERFETTO_DCHECK(ToColumnType<T>() == type_);
+    auto idx = row_map().Get(row);
+    return static_cast<const SparseVector<T>*>(sparse_vector_)->Get(idx);
+  }
 
-  const StringPool* string_pool_ = nullptr;
+  template <typename T>
+  void SetTyped(uint32_t row, T value) {
+    PERFETTO_DCHECK(ToColumnType<T>() == type_);
+    auto idx = row_map().Get(row);
+    return static_cast<SparseVector<T>*>(sparse_vector_)->Set(idx, value);
+  }
 
+  NullTermStringView GetStringPoolString(uint32_t row) const {
+    return string_pool_->Get(*GetTyped<StringPool::Id>(row));
+  }
+
+  // type_ is used to cast sparse_vector_ to the correct type.
   ColumnType type_ = ColumnType::kInt64;
-  union {
-    // Valid when |type_| == ColumnType::kUint32.
-    const SparseVector<uint32_t>* uint32_sv;
-
-    // Valid when |type_| == ColumnType::kInt64.
-    const SparseVector<int64_t>* int64_sv = nullptr;
-
-    // Valid when |type_| == ColumnType::kString.
-    const SparseVector<StringPool::Id>* string_sv;
-  } data_;
+  void* sparse_vector_ = nullptr;
 
  private:
   friend class Table;
@@ -215,74 +202,32 @@
          ColumnType type,
          Table* table,
          uint32_t col_idx,
-         uint32_t row_map_idx);
+         uint32_t row_map_idx,
+         void* sparse_vector);
 
   Column(const Column&) = delete;
   Column& operator=(const Column&) = delete;
 
+  template <typename T>
+  static ColumnType ToColumnType() {
+    if (std::is_same<T, uint32_t>::value) {
+      return ColumnType::kUint32;
+    } else if (std::is_same<T, int64_t>::value) {
+      return ColumnType::kInt64;
+    } else if (std::is_same<T, StringPool::Id>::value) {
+      return ColumnType::kString;
+    } else {
+      PERFETTO_FATAL("Unsupported type of column");
+    }
+  }
+
   const char* name_ = nullptr;
   const Table* table_ = nullptr;
   uint32_t col_idx_ = 0;
   uint32_t row_map_idx_ = 0;
+  const StringPool* string_pool_ = nullptr;
 };
 
-// The below Get* functions need to be defined out of line as GCC does not
-// allow explicit specialisation inside a class scope.
-template <>
-inline base::Optional<uint32_t> Column::GetTyped<uint32_t>(uint32_t row) const {
-  PERFETTO_DCHECK(type_ == ColumnType::kUint32);
-  auto idx = row_map().Get(row);
-  return data_.uint32_sv->Get(idx);
-}
-
-template <>
-inline base::Optional<int64_t> Column::GetTyped<int64_t>(uint32_t row) const {
-  PERFETTO_DCHECK(type_ == ColumnType::kInt64);
-  auto idx = row_map().Get(row);
-  return data_.int64_sv->Get(idx);
-}
-
-template <>
-inline base::Optional<StringPool::Id> Column::GetTyped<StringPool::Id>(
-    uint32_t row) const {
-  PERFETTO_DCHECK(type_ == ColumnType::kString);
-  auto idx = row_map().Get(row);
-  return data_.string_sv->Get(idx);
-}
-
-inline NullTermStringView Column::GetString(uint32_t row) const {
-  auto opt_id = GetTyped<StringPool::Id>(row);
-  // We DCHECK here because although we are using SparseVector, the null
-  // info is handled by the StringPool rather than by the SparseVector.
-  // The value returned by the SparseVector should always be non-null.
-  // TODO(lalitm): investigate removing this check if/when we support
-  // std::deque<StringId>.
-  PERFETTO_DCHECK(opt_id.has_value());
-  return string_pool_->Get(*opt_id);
-}
-
-// This function needs to be defined out of line as it relies on the explicit
-// specialisations defined above (which are themselves out of line).
-inline SqlValue Column::Get(uint32_t row) const {
-  switch (type_) {
-    case ColumnType::kUint32: {
-      auto opt_value = GetTyped<uint32_t>(row);
-      return opt_value ? SqlValue::Long(*opt_value) : SqlValue();
-    }
-    case ColumnType::kInt64: {
-      auto opt_value = GetTyped<int64_t>(row);
-      return opt_value ? SqlValue::Long(*opt_value) : SqlValue();
-    }
-    case ColumnType::kString: {
-      auto str = GetString(row).c_str();
-      return str == nullptr ? SqlValue() : SqlValue::String(str);
-    }
-    case ColumnType::kId:
-      return SqlValue::Long(row_map().Get(row));
-  }
-  PERFETTO_FATAL("For GCC");
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index beb73e5..1bafb4f 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -19,7 +19,7 @@
 namespace perfetto {
 namespace trace_processor {
 
-Table::Table(const StringPool* pool, const Table* parent) : string_pool_(pool) {
+Table::Table(StringPool* pool, const Table* parent) : string_pool_(pool) {
   if (!parent)
     return;
 
@@ -32,19 +32,23 @@
 }
 
 Table& Table::operator=(const Table& other) {
+  size_ = other.size_;
+  string_pool_ = other.string_pool_;
+
   for (const RowMap& rm : other.row_maps_) {
     row_maps_.emplace_back(rm.Copy());
   }
-  size_ = other.size_;
   for (const Column& col : other.columns_)
     columns_.emplace_back(col, this, columns_.size(), col.row_map_idx_);
   return *this;
 }
 
 Table& Table::operator=(Table&& other) noexcept {
+  size_ = other.size_;
+  string_pool_ = other.string_pool_;
+
   row_maps_ = std::move(other.row_maps_);
   columns_ = std::move(other.columns_);
-  size_ = other.size_;
   for (Column& col : columns_) {
     col.table_ = this;
   }
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 66eb856..51ad822 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -104,12 +104,14 @@
   const std::vector<RowMap>& row_maps() const { return row_maps_; }
 
  protected:
-  Table(const StringPool* pool, const Table* parent);
+  Table(StringPool* pool, const Table* parent);
 
   std::vector<RowMap> row_maps_;
   std::vector<Column> columns_;
   uint32_t size_ = 0;
 
+  StringPool* string_pool_ = nullptr;
+
  private:
   friend class Column;
 
@@ -117,8 +119,6 @@
   // the Table pointer in each column to the Table being copied into.
   Table(const Table& other) { *this = other; }
   Table& operator=(const Table& other);
-
-  const StringPool* string_pool_ = nullptr;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index 57b4995..d07d034 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -33,6 +33,7 @@
   using StoredType = T;
 
   T operator[](uint32_t row) const { return *GetTyped<T>(row); }
+  void Set(uint32_t row, T value) { SetTyped(row, value); }
 };
 
 template <typename T>
@@ -40,13 +41,20 @@
   using StoredType = T;
 
   base::Optional<T> operator[](uint32_t row) const { return GetTyped<T>(row); }
+  void Set(uint32_t row, T value) { SetTyped(row, value); }
 };
 
 template <>
 struct TypedColumn<StringPool::Id> : public Column {
   using StoredType = StringPool::Id;
 
-  NullTermStringView operator[](uint32_t row) const { return GetString(row); }
+  StringPool::Id operator[](uint32_t row) const {
+    return *GetTyped<StringPool::Id>(row);
+  }
+  NullTermStringView GetString(uint32_t row) const {
+    return GetStringPoolString(row);
+  }
+  void Set(uint32_t row, StringPool::Id value) { SetTyped(row, value); }
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index adb73f5..28dffb6 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -20,6 +20,7 @@
 #include <json/writer.h>
 #include <stdio.h>
 #include <cstring>
+#include <vector>
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "src/trace_processor/export_json.h"
@@ -599,6 +600,76 @@
   return kResultOk;
 }
 
+ResultCode ExportCpuProfileSamples(const TraceStorage* storage,
+                                   TraceFormatWriter* writer) {
+  const TraceStorage::CpuProfileStackSamples& samples =
+      storage->cpu_profile_stack_samples();
+  for (uint32_t i = 0; i < samples.size(); ++i) {
+    Json::Value event;
+    event["ts"] = Json::Int64(samples.timestamps()[i] / 1000);
+
+    UniqueTid utid = static_cast<UniqueTid>(samples.utids()[i]);
+    auto thread = storage->GetThread(utid);
+    event["tid"] = thread.tid;
+    if (thread.upid)
+      event["pid"] = storage->GetProcess(*thread.upid).pid;
+
+    event["ph"] = "I";
+    event["cat"] = "disabled_by_default-cpu_profiler";
+    event["name"] = "StackCpuSampling";
+    event["scope"] = "t";
+
+    std::vector<std::string> callstack;
+    const TraceStorage::StackProfileCallsites& callsites =
+        storage->stack_profile_callsites();
+    int64_t maybe_callsite_id = samples.callsite_ids()[i];
+    PERFETTO_DCHECK(maybe_callsite_id >= 0 &&
+                    maybe_callsite_id < callsites.size());
+    while (maybe_callsite_id >= 0) {
+      size_t callsite_id = static_cast<size_t>(maybe_callsite_id);
+
+      const TraceStorage::StackProfileFrames& frames =
+          storage->stack_profile_frames();
+      PERFETTO_DCHECK(callsites.frame_ids()[callsite_id] >= 0 &&
+                      callsites.frame_ids()[callsite_id] < frames.size());
+      size_t frame_id = static_cast<size_t>(callsites.frame_ids()[callsite_id]);
+
+      const TraceStorage::StackProfileMappings& mappings =
+          storage->stack_profile_mappings();
+      PERFETTO_DCHECK(frames.mappings()[frame_id] >= 0 &&
+                      frames.mappings()[frame_id] < mappings.size());
+      size_t mapping_id = static_cast<size_t>(frames.mappings()[frame_id]);
+
+      NullTermStringView symbol_name =
+          storage->GetString(frames.names()[frame_id]);
+
+      char frame_entry[1024];
+      snprintf(
+          frame_entry, sizeof(frame_entry), "%s - %s [%s]\n",
+          (symbol_name.empty()
+               ? PrintUint64(static_cast<uint64_t>(frames.rel_pcs()[frame_id]))
+                     .c_str()
+               : symbol_name.c_str()),
+          storage->GetString(mappings.names()[mapping_id]).c_str(),
+          storage->GetString(mappings.build_ids()[mapping_id]).c_str());
+
+      callstack.emplace_back(frame_entry);
+
+      maybe_callsite_id = callsites.parent_callsite_ids()[callsite_id];
+    }
+
+    std::string merged_callstack;
+    for (auto entry = callstack.rbegin(); entry != callstack.rend(); ++entry) {
+      merged_callstack += *entry;
+    }
+
+    event["args"]["frames"] = merged_callstack;
+    writer->WriteCommonEvent(event);
+  }
+
+  return kResultOk;
+}
+
 ResultCode ExportMetadata(const TraceStorage* storage,
                           TraceFormatWriter* writer) {
   const auto& trace_metadata = storage->metadata();
@@ -758,6 +829,10 @@
   if (code != kResultOk)
     return code;
 
+  code = ExportCpuProfileSamples(storage, &writer);
+  if (code != kResultOk)
+    return code;
+
   code = ExportMetadata(storage, &writer);
   if (code != kResultOk)
     return code;
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index ea04091..c4cac83 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -637,7 +637,8 @@
   UniquePid upid = storage.AddEmptyProcess(kProcessID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  TrackId track_id = storage.mutable_tracks()->AddTrack(name_id);
+  TrackId track_id =
+      storage.mutable_track_table()->Insert(tables::TrackTable::Row(name_id));
   storage.mutable_virtual_tracks()->AddVirtualTrack(
       track_id, VirtualTrackScope::kProcess, upid);
   storage.mutable_nestable_slices()->AddSlice(kTimestamp, kDuration, track_id,
@@ -698,7 +699,8 @@
   UniquePid upid = storage.AddEmptyProcess(kProcessID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  TrackId track_id = storage.mutable_tracks()->AddTrack(name_id);
+  TrackId track_id =
+      storage.mutable_track_table()->Insert(tables::TrackTable::Row(name_id));
   storage.mutable_virtual_tracks()->AddVirtualTrack(
       track_id, VirtualTrackScope::kProcess, upid);
   auto slice_id = storage.mutable_nestable_slices()->AddSlice(
@@ -753,7 +755,8 @@
   UniquePid upid = storage.AddEmptyProcess(kProcessID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  TrackId track_id = storage.mutable_tracks()->AddTrack(name_id);
+  TrackId track_id =
+      storage.mutable_track_table()->Insert(tables::TrackTable::Row(name_id));
   storage.mutable_virtual_tracks()->AddVirtualTrack(
       track_id, VirtualTrackScope::kProcess, upid);
   auto slice_id = storage.mutable_nestable_slices()->AddSlice(
@@ -796,7 +799,8 @@
   UniquePid upid = storage.AddEmptyProcess(kProcessID);
   StringId cat_id = storage.InternString(base::StringView(kCategory));
   StringId name_id = storage.InternString(base::StringView(kName));
-  TrackId track_id = storage.mutable_tracks()->AddTrack(name_id);
+  TrackId track_id =
+      storage.mutable_track_table()->Insert(tables::TrackTable::Row(name_id));
   storage.mutable_virtual_tracks()->AddVirtualTrack(
       track_id, VirtualTrackScope::kProcess, upid);
   storage.mutable_nestable_slices()->AddSlice(
@@ -967,6 +971,67 @@
   EXPECT_EQ(result["systemTraceEvents"].asString(), kLegacyFtraceData);
 }
 
+TEST(ExportJsonTest, CpuProfileEvent) {
+  const int64_t kProcessID = 100;
+  const int64_t kThreadID = 200;
+  const int64_t kTimestamp = 10000000;
+
+  TraceProcessorContext context;
+  context.storage.reset(new TraceStorage());
+  TraceStorage* storage = context.storage.get();
+
+  UniquePid upid = storage->AddEmptyProcess(kProcessID);
+  UniqueTid utid = storage->AddEmptyThread(kThreadID);
+  storage->GetMutableThread(utid)->upid = upid;
+
+  RowId module_row_id_1 = storage->mutable_stack_profile_mappings()->Insert(
+      {storage->InternString("foo_module_id"), 0, 0, 0, 0, 0,
+       storage->InternString("foo_module_name")});
+
+  RowId module_row_id_2 = storage->mutable_stack_profile_mappings()->Insert(
+      {storage->InternString("bar_module_id"), 0, 0, 0, 0, 0,
+       storage->InternString("bar_module_name")});
+
+  RowId frame_row_id_1 = storage->mutable_stack_profile_frames()->Insert(
+      {storage->InternString("foo_func"), module_row_id_1, 0x42});
+
+  RowId frame_row_id_2 = storage->mutable_stack_profile_frames()->Insert(
+      {storage->InternString("bar_func"), module_row_id_2, 0x4242});
+
+  RowId frame_callsite_id_1 =
+      storage->mutable_stack_profile_callsites()->Insert(
+          {0, -1, frame_row_id_1});
+
+  RowId frame_callsite_id_2 =
+      storage->mutable_stack_profile_callsites()->Insert(
+          {1, frame_callsite_id_1, frame_row_id_2});
+
+  storage->mutable_cpu_profile_stack_samples()->Insert(
+      {kTimestamp, frame_callsite_id_2, utid});
+
+  base::TempFile temp_file = base::TempFile::Create();
+  FILE* output = fopen(temp_file.path().c_str(), "w+");
+  int code = ExportJson(storage, output);
+
+  EXPECT_EQ(code, kResultOk);
+
+  Json::Reader reader;
+  Json::Value result;
+  EXPECT_TRUE(reader.parse(ReadFile(output), result));
+
+  EXPECT_EQ(result["traceEvents"].size(), 1u);
+  Json::Value event = result["traceEvents"][0];
+  EXPECT_EQ(event["ph"].asString(), "I");
+  EXPECT_EQ(event["ts"].asInt64(), kTimestamp / 1000);
+  EXPECT_EQ(event["tid"].asUInt(), kThreadID);
+  EXPECT_EQ(event["cat"].asString(), "disabled_by_default-cpu_profiler");
+  EXPECT_EQ(event["name"].asString(), "StackCpuSampling");
+  EXPECT_EQ(event["scope"].asString(), "t");
+  EXPECT_EQ(event["args"]["frames"].asString(),
+            "foo_func - foo_module_name [foo_module_id]\nbar_func - "
+            "bar_module_name [bar_module_id]\n");
+}
+
 }  // namespace
 }  // namespace json
 }  // namespace trace_processor
diff --git a/src/trace_processor/filtered_row_index.h b/src/trace_processor/filtered_row_index.h
index d8dcaeb..c17fc56 100644
--- a/src/trace_processor/filtered_row_index.h
+++ b/src/trace_processor/filtered_row_index.h
@@ -122,16 +122,14 @@
 
   template <typename Predicate>
   void FilterRowVector(Predicate fn) {
-    size_t rows_size = rows_.size();
-    for (size_t i = 0; i < rows_size;) {
-      if (fn(rows_[i])) {
-        i++;
-      } else {
-        std::swap(rows_[i], rows_[rows_size - 1]);
-        rows_size--;
+    size_t write_idx = 0;
+    for (size_t read_idx = 0; read_idx < rows_.size(); ++read_idx) {
+      uint32_t row = rows_[read_idx];
+      if (fn(row)) {
+        rows_[write_idx++] = row;
       }
     }
-    rows_.resize(rows_size);
+    rows_.resize(write_idx);
   }
 
   void ConvertBitVectorToRowVector();
diff --git a/src/trace_processor/ftrace_descriptors.cc b/src/trace_processor/ftrace_descriptors.cc
index bad9305..4540df1 100644
--- a/src/trace_processor/ftrace_descriptors.cc
+++ b/src/trace_processor/ftrace_descriptors.cc
@@ -8,7 +8,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<MessageDescriptor, 336> descriptors{{
+std::array<MessageDescriptor, 333> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -3537,41 +3537,6 @@
             {"state", ProtoSchemaType::kUint32},
         },
     },
-    {
-        "gpu_sched_enqueue",
-        4,
-        {
-            {},
-            {"ctx_id", ProtoSchemaType::kUint32},
-            {"job_id", ProtoSchemaType::kUint32},
-            {"priority", ProtoSchemaType::kUint32},
-            {"submission_id", ProtoSchemaType::kUint32},
-        },
-    },
-    {
-        "gpu_sched_submit",
-        5,
-        {
-            {},
-            {"ctx_id", ProtoSchemaType::kUint32},
-            {"hwqueue_id", ProtoSchemaType::kUint32},
-            {"job_id", ProtoSchemaType::kUint32},
-            {"priority", ProtoSchemaType::kUint32},
-            {"submission_id", ProtoSchemaType::kUint32},
-        },
-    },
-    {
-        "gpu_sched_complete",
-        5,
-        {
-            {},
-            {"ctx_id", ProtoSchemaType::kUint32},
-            {"job_id", ProtoSchemaType::kUint32},
-            {"msg", ProtoSchemaType::kString},
-            {"priority", ProtoSchemaType::kUint32},
-            {"submission_id", ProtoSchemaType::kUint32},
-        },
-    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/graphics_frame_event_parser.cc b/src/trace_processor/graphics_frame_event_parser.cc
index e5f0649..6913771 100644
--- a/src/trace_processor/graphics_frame_event_parser.cc
+++ b/src/trace_processor/graphics_frame_event_parser.cc
@@ -127,7 +127,7 @@
       track_name_id);
 
   context_->storage->mutable_gpu_track_table()->Insert(
-      track_id, graphics_event_scope_id_, base::nullopt /* context */);
+      tables::GpuTrackTable::Row(track_id, graphics_event_scope_id_));
 
   const auto slice_id = context_->slice_tracker->Scoped(
       timestamp, track_id, RefType::kRefTrack, 0 /* cat */, event_name_id,
@@ -137,10 +137,10 @@
       });
 
   if (slice_id) {
-    context_->storage->mutable_gpu_slice_table()->Insert(
-        slice_id.value(), base::nullopt /* context_id */,
-        base::nullopt /* render_target */, frame_number,
-        base::nullopt /* job_id */, base::nullopt /* hw_queue_id */);
+    tables::GpuSliceTable::Row row;
+    row.slice_id = slice_id.value();
+    row.frame_id = frame_number;
+    context_->storage->mutable_gpu_slice_table()->Insert(row);
   }
 }
 
diff --git a/src/trace_processor/metrics/android/android_cpu.sql b/src/trace_processor/metrics/android/android_cpu.sql
index 91f754c..ef6dc82 100644
--- a/src/trace_processor/metrics/android/android_cpu.sql
+++ b/src/trace_processor/metrics/android/android_cpu.sql
@@ -20,6 +20,7 @@
 CREATE VIEW cpu_breakdown_per_utid AS
 SELECT
   utid,
+  CAST(SUM(dur * freq / 1000000) AS INT) AS normalized_cpu_cycles,
   AndroidCpuMetric_CpuFrequencyData(
     'id', cpu,
     'avg_freq_khz', CAST((SUM(dur * freq) / SUM(dur)) AS INT),
@@ -38,6 +39,7 @@
   upid,
   thread.name AS thread_name,
   process.name AS process_name,
+  CAST(SUM(normalized_cpu_cycles) AS INT) AS normalized_cpu_cycles,
   RepeatedField(cpu_freq_proto) AS thread_proto_cpu
 FROM thread
 JOIN process USING (upid)
@@ -50,10 +52,12 @@
 SELECT
   upid,
   process_name,
+  CAST(SUM(normalized_cpu_cycles) AS INT) AS normalized_cpu_cycles,
   RepeatedField(
     AndroidCpuMetric_Thread(
       'name', CAST(thread_name as TEXT),
-      'cpu', thread_proto_cpu
+      'cpu', thread_proto_cpu,
+      'normalized_cpu_cycles', CAST(normalized_cpu_cycles as INT)
     )
   ) AS thread_proto
 FROM agg_by_thread
@@ -64,7 +68,8 @@
 SELECT
   AndroidCpuMetric_Process(
     'name', process_name,
-    'threads', thread_proto
+    'threads', thread_proto,
+    'normalized_cpu_cycles', CAST(normalized_cpu_cycles AS INT)
   ) AS cpu_info_process
 FROM agg_by_process
 GROUP BY upid;
diff --git a/src/trace_processor/metrics/metrics.descriptor.h b/src/trace_processor/metrics/metrics.descriptor.h
index 8a5334e..bc84af0 100644
--- a/src/trace_processor/metrics/metrics.descriptor.h
+++ b/src/trace_processor/metrics/metrics.descriptor.h
@@ -19,7 +19,7 @@
 
 namespace perfetto {
 
-constexpr std::array<uint8_t, 9039> kMetricsDescriptor{
+constexpr std::array<uint8_t, 9145> kMetricsDescriptor{
     {0x0a, 0x98, 0x03, 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,
@@ -54,13 +54,13 @@
      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, 0x42,
-     0x02, 0x48, 0x03, 0x0a, 0x9d, 0x04, 0x0a, 0x30, 0x70, 0x72, 0x6f, 0x74,
+     0x02, 0x48, 0x03, 0x0a, 0x87, 0x05, 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, 0xd3, 0x03, 0x0a, 0x10, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+     0x73, 0x22, 0xbd, 0x04, 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,
@@ -82,698 +82,707 @@
      0x0a, 0x61, 0x76, 0x67, 0x46, 0x72, 0x65, 0x71, 0x4b, 0x68, 0x7a, 0x12,
      0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
      0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75,
-     0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x73, 0x1a, 0x62, 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, 0x44, 0x0a, 0x03, 0x63, 0x70, 0x75, 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, 0x41, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74, 0x72,
-     0x69, 0x63, 0x2e, 0x43, 0x70, 0x75, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65,
-     0x6e, 0x63, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x03, 0x63, 0x70, 0x75,
-     0x1a, 0x61, 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, 0x42, 0x0a, 0x07, 0x74,
-     0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x18, 0x02, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x93, 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,
+     0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x73, 0x1a, 0x96, 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, 0x44, 0x0a, 0x03, 0x63, 0x70, 0x75, 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, 0x41,
+     0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x43, 0x70, 0x75, 0x4d, 0x65, 0x74,
+     0x72, 0x69, 0x63, 0x2e, 0x43, 0x70, 0x75, 0x46, 0x72, 0x65, 0x71, 0x75,
+     0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x03, 0x63, 0x70,
+     0x75, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69,
+     0x7a, 0x65, 0x64, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x79, 0x63, 0x6c,
+     0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6e, 0x6f,
+     0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x70, 0x75, 0x43,
+     0x79, 0x63, 0x6c, 0x65, 0x73, 0x1a, 0x95, 0x01, 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, 0x42, 0x0a, 0x07, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73,
+     0x18, 0x02, 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, 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,
+     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, 0x32, 0x0a,
+     0x15, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f,
+     0x63, 0x70, 0x75, 0x5f, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x18, 0x03,
+     0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c,
+     0x69, 0x7a, 0x65, 0x64, 0x43, 0x70, 0x75, 0x43, 0x79, 0x63, 0x6c, 0x65,
+     0x73, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x93, 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, 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, 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, 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,
+     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, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0xa4, 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,
+     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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0xa4, 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, 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,
+     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,
-     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, 0x42, 0x02, 0x48, 0x03,
-     0x0a, 0x90, 0x04, 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, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x67, 0x72, 0x6f,
-     0x77, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70,
-     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-     0x6f, 0x73, 0x22, 0xc2, 0x03, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f,
-     0x77, 0x74, 0x68, 0x12, 0x60, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61,
-     0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18,
-     0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     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, 0x50, 0x72, 0x6f, 0x63, 0x65,
-     0x73, 0x73, 0x47, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x73,
-     0x74, 0x61, 0x6e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-     0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4d, 0x65,
-     0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0xc7, 0x02, 0x0a, 0x0f, 0x49, 0x6e,
-     0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-     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, 0x38, 0x0a, 0x19, 0x61, 0x6e,
-     0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f,
-     0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
-     0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x61, 0x6e, 0x6f, 0x6e, 0x41,
-     0x6e, 0x64, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x72, 0x74, 0x56,
-     0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x1a, 0x61, 0x6e, 0x6f, 0x6e,
-     0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x63, 0x68,
-     0x61, 0x6e, 0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x61, 0x6e, 0x6f, 0x6e, 0x41, 0x6e,
-     0x64, 0x53, 0x77, 0x61, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x42,
-     0x79, 0x74, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6d, 0x61, 0x6c, 0x6c,
-     0x6f, 0x63, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x63, 0x68,
-     0x61, 0x6e, 0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04,
-     0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x6d, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
-     0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
-     0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x23, 0x6d, 0x61, 0x6c,
-     0x6c, 0x6f, 0x63, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x74,
-     0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
-     0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01,
-     0x28, 0x03, 0x52, 0x1f, 0x6d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x4d, 0x65,
-     0x6d, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c,
-     0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x42,
-     0x02, 0x48, 0x03, 0x0a, 0xaf, 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, 0xe5, 0x01, 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, 0x8e, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x95, 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, 0xcb, 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, 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, 0x42, 0x02, 0x48,
-     0x03, 0x0a, 0xf4, 0x02, 0x0a, 0x35, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x90,
+     0x04, 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, 0x70,
+     0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x67, 0x72, 0x6f, 0x77, 0x74,
+     0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x22, 0xc2, 0x03, 0x0a, 0x14, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f, 0x77, 0x74,
+     0x68, 0x12, 0x60, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
+     0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 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, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x47, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61,
+     0x6e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x0f,
+     0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x72,
+     0x69, 0x63, 0x73, 0x1a, 0xc7, 0x02, 0x0a, 0x0f, 0x49, 0x6e, 0x73, 0x74,
+     0x61, 0x6e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 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, 0x38, 0x0a, 0x19, 0x61, 0x6e, 0x6f, 0x6e,
+     0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, 0x74,
+     0x61, 0x72, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20,
+     0x01, 0x28, 0x03, 0x52, 0x15, 0x61, 0x6e, 0x6f, 0x6e, 0x41, 0x6e, 0x64,
+     0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x72, 0x74, 0x56, 0x61, 0x6c,
+     0x75, 0x65, 0x12, 0x3a, 0x0a, 0x1a, 0x61, 0x6e, 0x6f, 0x6e, 0x5f, 0x61,
+     0x6e, 0x64, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x6e,
+     0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x16, 0x61, 0x6e, 0x6f, 0x6e, 0x41, 0x6e, 0x64, 0x53,
+     0x77, 0x61, 0x70, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x79, 0x74,
+     0x65, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6d, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
+     0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x63, 0x68, 0x61, 0x6e,
+     0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01,
+     0x28, 0x03, 0x52, 0x17, 0x6d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x4d, 0x65,
+     0x6d, 0x6f, 0x72, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x79,
+     0x74, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x23, 0x6d, 0x61, 0x6c, 0x6c, 0x6f,
+     0x63, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x74,
+     0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64,
+     0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
+     0x52, 0x1f, 0x6d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x4d, 0x65, 0x6d, 0x6f,
+     0x72, 0x79, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
+     0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x42, 0x02, 0x48,
+     0x03, 0x0a, 0xaf, 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, 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,
+     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,
+     0xe5, 0x01, 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, 0x8e, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x95, 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, 0xcb, 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, 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, 0x42, 0x02, 0x48, 0x03, 0x0a,
+     0xf4, 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,
-     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,
-     0x42, 0x02, 0x48, 0x03, 0x0a, 0xac, 0x0e, 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, 0x22, 0xde, 0x0d, 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, 0x1e, 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, 0x1a, 0xbb, 0x08, 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, 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,
+     0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a,
+     0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x44, 0x61, 0x74, 0x61, 0x42, 0x02,
+     0x48, 0x03, 0x0a, 0xac, 0x0e, 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, 0x22, 0xde, 0x0d, 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, 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,
+     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, 0x1e, 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, 0x1a, 0xbb, 0x08, 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, 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, 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,
+     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, 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,
+     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, 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, 0x4b, 0x0a, 0x23, 0x6f, 0x74, 0x68, 0x65,
-     0x72, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f,
-     0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x70,
-     0x75, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28,
-     0x01, 0x52, 0x1e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x54, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74,
-     0x79, 0x43, 0x70, 0x75, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x1a, 0xbb, 0x02,
-     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,
+     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, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0xf5, 0x05, 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, 0x68, 0x65, 0x61, 0x70,
-     0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c,
-     0x6c, 0x73, 0x69, 0x74, 0x65, 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, 0x22, 0x9a,
-     0x05, 0x0a, 0x18, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74,
-     0x61, 0x74, 0x73, 0x12, 0x5e, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61,
-     0x6e, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 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, 0x48, 0x65,
-     0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c,
-     0x6c, 0x73, 0x69, 0x74, 0x65, 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, 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, 0x51, 0x0a, 0x08, 0x43,
-     0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x66,
-     0x72, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f,
-     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,
-     0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52,
-     0x05, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x1a, 0xe3, 0x01, 0x0a, 0x0d, 0x43,
-     0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73,
-     0x12, 0x4e, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
-     0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72,
+     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, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
-     0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74,
-     0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x52, 0x08,
-     0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
-     0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
-     0x02, 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, 0x03, 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, 0x04, 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, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65,
-     0x6c, 0x74, 0x61, 0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0xa4, 0x01, 0x0a,
-     0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61,
-     0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 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, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63,
-     0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5e, 0x0a, 0x0e, 0x63,
-     0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74,
-     0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x70, 0x65,
+     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, 0x4b, 0x0a, 0x23, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f,
+     0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x61,
+     0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x70, 0x75, 0x5f,
+     0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52,
+     0x1e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
+     0x73, 0x54, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x43,
+     0x70, 0x75, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x1a, 0xbb, 0x02, 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, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
-     0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61,
-     0x74, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53,
-     0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69,
-     0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x02, 0x48, 0x03, 0x0a,
-     0x8c, 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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0xca, 0x0e, 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,
+     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, 0x42, 0x02, 0x48, 0x03, 0x0a, 0xf5, 0x05, 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, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70,
+     0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73,
+     0x69, 0x74, 0x65, 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, 0x22, 0x9a, 0x05, 0x0a,
+     0x18, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
+     0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74,
+     0x73, 0x12, 0x5e, 0x0a, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
+     0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 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, 0x48, 0x65, 0x61, 0x70,
+     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
+     0x69, 0x74, 0x65, 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, 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, 0x51, 0x0a, 0x08, 0x43, 0x61, 0x6c,
+     0x6c, 0x73, 0x69, 0x74, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x66, 0x72, 0x61,
+     0x6d, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x53, 0x74,
+     0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x05, 0x66,
+     0x72, 0x61, 0x6d, 0x65, 0x1a, 0xe3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x6c,
+     0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x4e,
+     0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x18, 0x01,
+     0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 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, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e,
+     0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x52, 0x08, 0x63, 0x61,
+     0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f,
+     0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 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, 0x03, 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, 0x04, 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, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x74,
+     0x61, 0x42, 0x79, 0x74, 0x65, 0x73, 0x1a, 0xa4, 0x01, 0x0a, 0x0d, 0x49,
+     0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73,
+     0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 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, 0x01,
+     0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
+     0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5e, 0x0a, 0x0e, 0x63, 0x61, 0x6c,
+     0x6c, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18,
+     0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 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, 0x53, 0x74, 0x61, 0x74, 0x73,
+     0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x74, 0x61,
+     0x74, 0x73, 0x52, 0x0d, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x02, 0x48, 0x03, 0x0a, 0x8c, 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,
+     0x42, 0x02, 0x48, 0x03, 0x0a, 0xca, 0x0e, 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, 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, 0x30,
+     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,
-     0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-     0x1a, 0x36, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x70, 0x65, 0x72,
+     0x75, 0x6e, 0x61, 0x67, 0x67, 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, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f,
+     0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 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, 0x75, 0x6e, 0x61, 0x67, 0x67, 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, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x5f, 0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f,
+     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, 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, 0x30, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x6c, 0x6d, 0x6b, 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, 0x6c, 0x6d, 0x6b, 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, 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, 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, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72,
-     0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69,
-     0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 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, 0x22, 0xd2, 0x01, 0x0a, 0x0d, 0x54,
-     0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-     0x12, 0x50, 0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x74,
-     0x61, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20,
-     0x03, 0x28, 0x0b, 0x32, 0x24, 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, 0x2e,
-     0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x65, 0x72, 0x72, 0x6f, 0x72,
-     0x53, 0x74, 0x61, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 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, 0x1a, 0x43, 0x0a, 0x05, 0x45, 0x6e,
-     0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-     0x10, 0x0a, 0x03, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
-     0x52, 0x03, 0x69, 0x64, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
-     0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61,
-     0x6c, 0x75, 0x65, 0x22, 0xe9, 0x07, 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,
+     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, 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, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x66,
+     0x69, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
+     0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 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, 0x22, 0xd2, 0x01, 0x0a, 0x0d, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x50,
+     0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74,
+     0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28,
+     0x0b, 0x32, 0x24, 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, 0x2e, 0x45, 0x6e,
+     0x74, 0x72, 0x79, 0x52, 0x0f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x53, 0x74,
+     0x61, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 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, 0x1a, 0x43, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72,
+     0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+     0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a,
+     0x03, 0x69, 0x64, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03,
+     0x69, 0x64, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+     0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+     0x65, 0x22, 0xe9, 0x07, 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, 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,
+     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, 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, 0x5b, 0x0a, 0x16, 0x61, 0x6e,
-     0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-     0x73, 0x5f, 0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x18, 0x0a, 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, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47,
-     0x72, 0x6f, 0x77, 0x74, 0x68, 0x52, 0x14, 0x61, 0x6e, 0x64, 0x72, 0x6f,
-     0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f,
-     0x77, 0x74, 0x68, 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,
+     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, 0x5b, 0x0a, 0x16, 0x61, 0x6e, 0x64, 0x72,
+     0x6f, 0x69, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f,
+     0x67, 0x72, 0x6f, 0x77, 0x74, 0x68, 0x18, 0x0a, 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, 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,
+     0x69, 0x64, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f,
+     0x77, 0x74, 0x68, 0x52, 0x14, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+     0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x6f, 0x77, 0x74,
+     0x68, 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, 0x68, 0x0a, 0x1b,
+     0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
+     0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x74,
+     0x61, 0x74, 0x73, 0x18, 0x0e, 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, 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, 0x68,
-     0x0a, 0x1b, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69,
-     0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x5f,
-     0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32,
-     0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
-     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72,
+     0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x50, 0x72, 0x6f, 0x66,
+     0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x53,
+     0x74, 0x61, 0x74, 0x73, 0x52, 0x18, 0x68, 0x65, 0x61, 0x70, 0x50, 0x72,
      0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74,
-     0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x18, 0x68, 0x65, 0x61, 0x70,
-     0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x73,
-     0x69, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 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, 0x2a, 0x06, 0x08, 0xc2, 0x03,
-     0x10, 0xf4, 0x03, 0x2a, 0x06, 0x08, 0xf4, 0x03, 0x10, 0xe9, 0x07, 0x4a,
-     0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x0d, 0x10, 0x0e, 0x42,
-     0x02, 0x48, 0x03}};
+     0x65, 0x53, 0x74, 0x61, 0x74, 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, 0x2a, 0x06, 0x08, 0xc2, 0x03, 0x10, 0xf4,
+     0x03, 0x2a, 0x06, 0x08, 0xf4, 0x03, 0x10, 0xe9, 0x07, 0x4a, 0x04, 0x08,
+     0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x0d, 0x10, 0x0e, 0x42, 0x02, 0x48,
+     0x03}};
 
 }  // namespace perfetto
 
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index 298c2ab..a530d6a 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -451,7 +451,7 @@
   }
 
   if (packet.has_chrome_events()) {
-    ParseChromeEvents(packet.chrome_events());
+    ParseChromeEvents(ts, packet.chrome_events());
   }
 
   if (packet.has_perfetto_metatrace()) {
@@ -597,6 +597,11 @@
     auto tid = static_cast<uint32_t>(thd.tid());
     auto tgid = static_cast<uint32_t>(thd.tgid());
     context_->process_tracker->UpdateThread(tid, tgid);
+
+    if (thd.has_name()) {
+      StringId threadNameId = context_->storage->InternString(thd.name());
+      context_->process_tracker->UpdateThreadName(tid, threadNameId);
+    }
   }
 }
 
@@ -2360,13 +2365,13 @@
   }
 }
 
-void ProtoTraceParser::ParseChromeEvents(ConstBytes blob) {
+void ProtoTraceParser::ParseChromeEvents(int64_t ts, ConstBytes blob) {
   TraceStorage* storage = context_->storage.get();
   protos::pbzero::ChromeEventBundle::Decoder bundle(blob.data, blob.size);
   ArgsTracker args(context_);
   if (bundle.has_metadata()) {
     RowId row_id = storage->mutable_raw_events()->AddRawEvent(
-        0, raw_chrome_metadata_event_id_, 0, 0);
+        ts, raw_chrome_metadata_event_id_, 0, 0);
 
     // Metadata is proxied via a special event in the raw table to JSON export.
     for (auto it = bundle.metadata(); it; ++it) {
@@ -2392,7 +2397,7 @@
 
   if (bundle.has_legacy_ftrace_output()) {
     RowId row_id = storage->mutable_raw_events()->AddRawEvent(
-        0, raw_chrome_legacy_system_trace_event_id_, 0, 0);
+        ts, raw_chrome_legacy_system_trace_event_id_, 0, 0);
 
     std::string data;
     for (auto it = bundle.legacy_ftrace_output(); it; ++it) {
@@ -2412,7 +2417,7 @@
         continue;
       }
       RowId row_id = storage->mutable_raw_events()->AddRawEvent(
-          0, raw_chrome_legacy_user_trace_event_id_, 0, 0);
+          ts, raw_chrome_legacy_user_trace_event_id_, 0, 0);
       Variadic value =
           Variadic::Json(storage->InternString(legacy_trace.data()));
       args.AddArg(row_id, data_name_id_, data_name_id_, value);
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index f65ce10..35257a6 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -132,7 +132,7 @@
       ArgsTracker* args_tracker,
       RowId row);
   void ParseChromeBenchmarkMetadata(ConstBytes);
-  void ParseChromeEvents(ConstBytes);
+  void ParseChromeEvents(int64_t ts, ConstBytes);
   void ParseMetatraceEvent(int64_t ts, ConstBytes);
   void ParseGpuCounterEvent(int64_t ts, ConstBytes);
   void ParseGpuRenderStageEvent(int64_t ts, ConstBytes);
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index d7578b8..c97ce84 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -1124,9 +1124,9 @@
 
   context_.sorter->ExtractEventsForced();
 
-  EXPECT_EQ(storage_->tracks().track_count(), 2u);
-  EXPECT_EQ(storage_->tracks().names()[0], 2u);
-  EXPECT_EQ(storage_->tracks().names()[1], 4u);
+  EXPECT_EQ(storage_->track_table().size(), 2u);
+  EXPECT_EQ(storage_->track_table().name()[0], 2u);
+  EXPECT_EQ(storage_->track_table().name()[1], 4u);
   EXPECT_EQ(storage_->virtual_tracks().virtual_track_count(), 2u);
   EXPECT_EQ(storage_->virtual_tracks().track_ids()[0], 0u);
   EXPECT_EQ(storage_->virtual_tracks().track_ids()[1], 1u);
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index 8e97017..e47ff6b 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -628,6 +628,8 @@
     return;
   }
 
+  latest_timestamp_ = std::max(timestamp, latest_timestamp_);
+
   if (auto tt_delta_field =
           event_decoder.FindField(kThreadTimeDeltaUsFieldNumber)) {
     thread_timestamp = state->IncrementAndGetTrackEventThreadTimeNs(
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index a77c1c5..fa588bb 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -227,7 +227,11 @@
           writer->AppendUnsignedInt(value.uint_value & ((1 << 20) - 1));
         });
     writer->AppendString(" ino ");
-    write_value_at_index(MFA::kIInoFieldNumber - 1, write_value);
+    write_value_at_index(MFA::kIInoFieldNumber - 1,
+                         [writer](const Variadic& value) {
+                           PERFETTO_DCHECK(value.type == Variadic::Type::kUint);
+                           writer->AppendHexInt(value.uint_value);
+                         });
     writer->AppendString(" page=0000000000000000");
     writer->AppendString(" pfn=");
     write_value_at_index(MFA::kPfnFieldNumber - 1, write_value);
diff --git a/src/trace_processor/slice_table.cc b/src/trace_processor/slice_table.cc
index 95d8b7b..70a96f2 100644
--- a/src/trace_processor/slice_table.cc
+++ b/src/trace_processor/slice_table.cc
@@ -56,7 +56,7 @@
   // Only the string columns are handled by SQLite
   info->order_by_consumed = true;
   size_t name_index = schema().ColumnIndexFromName("name");
-  size_t cat_index = schema().ColumnIndexFromName("cat");
+  size_t cat_index = schema().ColumnIndexFromName("category");
   size_t ref_type_index = schema().ColumnIndexFromName("ref_type");
   for (size_t i = 0; i < qc.constraints().size(); i++) {
     auto col = static_cast<size_t>(qc.constraints()[i].iColumn);
diff --git a/src/trace_processor/stack_profile_tracker.cc b/src/trace_processor/stack_profile_tracker.cc
index 77e29d3..35e77db 100644
--- a/src/trace_processor/stack_profile_tracker.cc
+++ b/src/trace_processor/stack_profile_tracker.cc
@@ -19,25 +19,10 @@
 #include "src/trace_processor/trace_processor_context.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace {
-
-std::string ToHex(const char* build_id, size_t size) {
-  std::string hex_build_id(2 * size + 1, 'x');
-  for (size_t i = 0; i < size; ++i) {
-    // snprintf prints 3 characters, the two hex digits and a null byte. As we
-    // write left to write, we keep overwriting the nullbytes, except for the
-    // last call to snprintf.
-    snprintf(&(hex_build_id[2 * i]), 3, "%02hhx", build_id[i]);
-  }
-  // Remove the trailing nullbyte produced by the last snprintf.
-  hex_build_id.resize(2 * size);
-  return hex_build_id;
-}
-
-}  // namespace
 
 StackProfileTracker::InternLookup::~InternLookup() = default;
 
@@ -73,7 +58,7 @@
   StringId build_id = empty_;
   if (raw_build_id_str.size() > 0) {
     std::string hex_build_id =
-        ToHex(raw_build_id_str.c_str(), raw_build_id_str.size());
+        base::ToHex(raw_build_id_str.c_str(), raw_build_id_str.size());
     build_id = context_->storage->InternString(base::StringView(hex_build_id));
   }
 
diff --git a/src/trace_processor/systrace_trace_parser.cc b/src/trace_processor/systrace_trace_parser.cc
index a766e6f..3d81e52 100644
--- a/src/trace_processor/systrace_trace_parser.cc
+++ b/src/trace_processor/systrace_trace_parser.cc
@@ -98,26 +98,34 @@
     const std::string& buffer) {
   // An example line from buffer looks something like the following:
   // <idle>-0     (-----) [000] d..1 16500.715638: cpu_idle: state=0 cpu_id=0
+  //
+  // However, sometimes the tgid can be missing and buffer looks like this:
+  // <idle>-0     [000] ...2     0.002188: task_newtask: pid=1 ...
 
   auto task_idx = 16u;
   std::string task = SubstrTrim(buffer, 0, task_idx);
 
+  // Try and figure out whether tgid is present by searching for '(' but only
+  // if it occurs before the start of cpu (indiciated by '[') - this is because
+  // '(' can also occur in the args of an event.
   auto tgid_idx = buffer.find('(', task_idx + 1);
-  std::string pid_str = SubstrTrim(buffer, task_idx + 1, tgid_idx);
+  auto cpu_idx = buffer.find('[', task_idx + 1);
+  bool has_tgid = tgid_idx != std::string::npos && tgid_idx < cpu_idx;
+
+  auto pid_end = has_tgid ? cpu_idx : tgid_idx;
+  std::string pid_str = SubstrTrim(buffer, task_idx + 1, pid_end);
   auto pid = static_cast<uint32_t>(std::stoi(pid_str));
   context_->process_tracker->GetOrCreateThread(pid);
 
-  auto tgid_end = buffer.find(')', tgid_idx + 1);
-  std::string tgid_str = SubstrTrim(buffer, tgid_idx + 1, tgid_end);
-  auto tgid = tgid_str == "-----"
-                  ? base::nullopt
-                  : base::Optional<uint32_t>(
-                        static_cast<uint32_t>(std::stoi(tgid_str)));
-  if (tgid.has_value()) {
-    context_->process_tracker->UpdateThread(pid, tgid.value());
+  if (has_tgid) {
+    auto tgid_end = buffer.find(')', tgid_idx + 1);
+    std::string tgid_str = SubstrTrim(buffer, tgid_idx + 1, tgid_end);
+    if (tgid_str != "-----") {
+      context_->process_tracker->UpdateThread(
+          pid, static_cast<uint32_t>(std::stoi(tgid_str)));
+    }
   }
 
-  auto cpu_idx = buffer.find('[', tgid_end + 1);
   auto cpu_end = buffer.find(']', cpu_idx + 1);
   std::string cpu_str = SubstrTrim(buffer, cpu_idx + 1, cpu_end);
   auto cpu = static_cast<uint32_t>(std::stoi(cpu_str));
diff --git a/src/trace_processor/tables/macros.h b/src/trace_processor/tables/macros.h
index 0046502..86ded55 100644
--- a/src/trace_processor/tables/macros.h
+++ b/src/trace_processor/tables/macros.h
@@ -40,7 +40,7 @@
 //
 // Then we would invoke the macro as follows:
 // #define PERFETTO_TP_EVENT_TABLE_DEF(NAME, PARENT, C)
-//   NAME(EventTable)
+//   NAME(EventTable, "event")
 //   PERFETTO_TP_ROOT_TABLE(PARENT, C)
 //   C(int64_t, ts)
 //   C(uint32_t, arg_set_id)
@@ -57,7 +57,7 @@
 //
 // Then, we would invoke the macro as follows:
 // #define PERFETTO_TP_SLICE_TABLE_DEF(NAME, PARENT, C)
-//   NAME(ChildTable)
+//   NAME(SliceTable, "slice")
 //   PARENT(PERFETTO_TP_EVENT_TABLE_DEF, C)
 //   C(int64_t, dur)
 //   C(uint8_t, depth)
@@ -77,8 +77,8 @@
 //
 // This macro takes one argument: the full definition of the table; the
 // definition is a function macro taking three arguments:
-// 1. NAME, a function macro taking one argument: the name of the new class
-//    being defined.
+// 1. NAME, a function macro taking two argument: the name of the new class
+//    being defined and the name of the table when exposed to SQLite.
 // 2. PARENT, a function macro taking two arguments: a) the definition of
 //    the parent table if this table
 //    is a root table b) C, the third parameter of the macro definition (see
@@ -87,9 +87,9 @@
 // 3. C, a function macro taking two parameters: a) the type of a column
 //    b) the name of a column. This macro should be invoked as many times as
 //    there are columns in the table with the information about them.
-#define PERFETTO_TP_TABLE(DEF)      \
-  PERFETTO_TP_TABLE_INTERNAL(       \
-      PERFETTO_TP_TABLE_CLASS(DEF), \
+#define PERFETTO_TP_TABLE(DEF)                                   \
+  PERFETTO_TP_TABLE_INTERNAL(                                    \
+      PERFETTO_TP_TABLE_NAME(DEF), PERFETTO_TP_TABLE_CLASS(DEF), \
       PERFETTO_TP_TABLE_CLASS(PERFETTO_TP_PARENT_DEF(DEF)), DEF)
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 2591e0a..8db36c7 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -31,7 +31,16 @@
 // pointer to this class will always be null.
 class RootParentTable : public Table {
  public:
-  uint32_t Insert(std::nullptr_t) { PERFETTO_FATAL("Should not be called"); }
+  struct Row {
+   public:
+    Row(std::nullptr_t) {}
+
+    const char* type() const { return type_; }
+
+   protected:
+    const char* type_ = nullptr;
+  };
+  uint32_t Insert(const Row&) { PERFETTO_FATAL("Should not be called"); }
 };
 
 // The parent class for all macro generated tables.
@@ -39,16 +48,21 @@
 // code size.
 class MacroTable : public Table {
  public:
-  MacroTable(const StringPool* pool, Table* parent)
-      : Table(pool, parent), parent_(parent) {
+  MacroTable(const char* name, StringPool* pool, Table* parent)
+      : Table(pool, parent), name_(name), parent_(parent) {
     row_maps_.emplace_back(BitVector());
     if (!parent) {
       columns_.emplace_back(
           Column::IdColumn(this, static_cast<uint32_t>(columns_.size()),
                            static_cast<uint32_t>(row_maps_.size()) - 1));
+      columns_.emplace_back(
+          Column("type", &type_, this, static_cast<uint32_t>(columns_.size()),
+                 static_cast<uint32_t>(row_maps_.size()) - 1));
     }
   }
 
+  const char* table_name() const { return name_; }
+
  protected:
   void UpdateRowMapsAfterParentInsert() {
     if (parent_ != nullptr) {
@@ -64,7 +78,20 @@
     row_maps_.back().Add(size_++);
   }
 
+  // Stores the most specific "derived" type of this row in the table.
+  //
+  // For example, suppose a row is inserted into the gpu_slice table. This will
+  // also cause a row to be inserted into the slice table. For users querying
+  // the slice table, they will want to know the "real" type of this slice (i.e.
+  // they will want to see that the type is gpu_slice). This sparse vector
+  // stores precisely the real type.
+  //
+  // Only relevant for parentless tables. Will be empty and unreferenced by
+  // tables with parents.
+  SparseVector<StringPool::Id> type_;
+
  private:
+  const char* name_ = nullptr;
   Table* parent_ = nullptr;
 };
 
@@ -74,10 +101,15 @@
 #define PERFETTO_TP_NOOP(...)
 
 // Gets the class name from a table definition.
-#define PERFETTO_TP_EXTRACT_TABLE_CLASS(class_name) class_name
+#define PERFETTO_TP_EXTRACT_TABLE_CLASS(class_name, ...) class_name
 #define PERFETTO_TP_TABLE_CLASS(DEF) \
   DEF(PERFETTO_TP_EXTRACT_TABLE_CLASS, PERFETTO_TP_NOOP, PERFETTO_TP_NOOP)
 
+// Gets the table name from the table definition.
+#define PERFETTO_TP_EXTRACT_TABLE_NAME(_, table_name) table_name
+#define PERFETTO_TP_TABLE_NAME(DEF) \
+  DEF(PERFETTO_TP_EXTRACT_TABLE_NAME, PERFETTO_TP_NOOP, PERFETTO_TP_NOOP)
+
 // Gets the parent definition from a table definition.
 #define PERFETTO_TP_EXTRACT_PARENT_DEF(PARENT_DEF, _) PARENT_DEF
 #define PERFETTO_TP_PARENT_DEF(DEF) \
@@ -108,10 +140,26 @@
   PERFETTO_TP_ALL_COLUMNS(PERFETTO_TP_PARENT_DEF(DEF), FN)
 
 // Basic macros for extracting column info from a schema.
-#define PERFETTO_TP_TYPE_COMMA(type, name) type,
 #define PERFETTO_TP_NAME_COMMA(type, name) name,
 #define PERFETTO_TP_TYPE_NAME_COMMA(type, name) type name,
 
+// Constructor parameters of Table::Row.
+// We name this name_c to avoid a clash with the field names of
+// Table::Row.
+#define PERFETTO_TP_ROW_CONSTRUCTOR(type, name) type name##_c = {},
+
+// Constructor parameters for parent of Row.
+#define PERFETTO_TP_PARENT_ROW_CONSTRUCTOR(type, name) name##_c,
+
+// Initializes the members of Table::Row.
+#define PERFETTO_TP_ROW_INITIALIZER(type, name) name = name##_c;
+
+// Defines the variable in Table::Row.
+#define PERFETTO_TP_ROW_DEFINITION(type, name) type name = {};
+
+// Defines the parent row field in Insert.
+#define PERFETTO_TP_PARENT_ROW_INSERT(type, name) row.name,
+
 // Defines the member variable in the Table.
 #define PERFETTO_TP_TABLE_MEMBER(type, name) \
   SparseVector<TypedColumn<type>::StoredType> name##_;
@@ -121,8 +169,9 @@
   columns_.emplace_back(#name, &name##_, this, columns_.size(), \
                         row_maps_.size() - 1);
 
-// Inserts the a value into the corresponding column
-#define PERFETTO_TP_COLUMN_APPEND(type, name) name##_.Append(std::move(name));
+// Inserts the value into the corresponding column
+#define PERFETTO_TP_COLUMN_APPEND(type, name) \
+  name##_.Append(std::move(row.name));
 
 // Defines the accessor for a column.
 #define PERFETTO_TP_TABLE_COL_ACCESSOR(type, name)           \
@@ -133,14 +182,44 @@
 
 // Definition used as the parent of root tables.
 #define PERFETTO_TP_ROOT_TABLE_PARENT_DEF(NAME, PARENT, C) \
-  NAME(macros_internal::RootParentTable)
+  NAME(macros_internal::RootParentTable, "root")
 
 // For more general documentation, see PERFETTO_TP_TABLE in macros.h.
-#define PERFETTO_TP_TABLE_INTERNAL(class_name, parent_class_name, DEF)        \
+#define PERFETTO_TP_TABLE_INTERNAL(table_name, class_name, parent_class_name, \
+                                   DEF)                                       \
   class class_name : public macros_internal::MacroTable {                     \
    public:                                                                    \
-    class_name(const StringPool* pool, parent_class_name* parent)             \
-        : macros_internal::MacroTable(pool, parent), parent_(parent) {        \
+    struct Row : parent_class_name::Row {                                     \
+      /*                                                                      \
+       * Expands to Row(col_type1 col1_c, base::Optional<col_type2> col2_c,   \
+       * ...)                                                                 \
+       */                                                                     \
+      Row(PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_ROW_CONSTRUCTOR)           \
+              std::nullptr_t = nullptr)                                       \
+          : parent_class_name::Row(PERFETTO_TP_PARENT_COLUMNS(                \
+                DEF,                                                          \
+                PERFETTO_TP_PARENT_ROW_CONSTRUCTOR) nullptr) {                \
+        type_ = table_name;                                                   \
+                                                                              \
+        /* Expands to                                                         \
+         * col1 = col1_c;                                                     \
+         * col2 = col2_c;                                                     \
+         * ...                                                                \
+         */                                                                   \
+        PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_ROW_INITIALIZER)           \
+      }                                                                       \
+                                                                              \
+      /* Expands to                                                           \
+       * col_type1 col1 = {};                                                 \
+       * base::Optional<col_type2> col2 = {};                                 \
+       * ...                                                                  \
+       */                                                                     \
+      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_ROW_DEFINITION)              \
+    };                                                                        \
+                                                                              \
+    class_name(StringPool* pool, parent_class_name* parent)                   \
+        : macros_internal::MacroTable(table_name, pool, parent),              \
+          parent_(parent) {                                                   \
       /* Expands to                                                           \
        * columns_.emplace_back("col1", col1_, this, columns_.size(),          \
        *                       row_maps_.size() - 1);                         \
@@ -151,29 +230,30 @@
       PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_CONSTRUCTOR_COLUMN);   \
     }                                                                         \
                                                                               \
-    /* Expands to Insert(col_type1 col1, base::Optional<col_type2> col2, ...) \
-     */                                                                       \
-    uint32_t Insert(PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TYPE_NAME_COMMA) \
-                        std::nullptr_t = nullptr) {                           \
+    uint32_t Insert(const Row& row) {                                         \
       uint32_t id;                                                            \
       if (parent_ == nullptr) {                                               \
         id = size();                                                          \
+        type_.Append(string_pool_->InternString(row.type()));                 \
       } else {                                                                \
-        /* Expands to parent_->Insert(parent_col_1, parent_col_2, ...) */     \
-        id = parent_->Insert(                                                 \
-            PERFETTO_TP_PARENT_COLUMNS(DEF, PERFETTO_TP_NAME_COMMA) nullptr); \
+        id = parent_->Insert(row);                                            \
       }                                                                       \
       UpdateRowMapsAfterParentInsert();                                       \
                                                                               \
       /* Expands to                                                           \
-       * col1_.Append(col1);                                                  \
-       * col2_.Append(col2);                                                  \
+       * col1_.Append(row.col1);                                              \
+       * col2_.Append(row.col2);                                              \
        * ...                                                                  \
        */                                                                     \
       PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_COLUMN_APPEND);              \
       return id;                                                              \
     }                                                                         \
                                                                               \
+    const TypedColumn<StringPool::Id>& type() {                               \
+      return static_cast<const TypedColumn<StringPool::Id>&>(                 \
+          columns_[static_cast<uint32_t>(ColumnIndex::type)]);                \
+    }                                                                         \
+                                                                              \
     /* Expands to                                                             \
      * const SparseVector<col1_type>& col1() { return col1_; }                \
      * const SparseVector<col2_type>& col2() { return col2_; }                \
@@ -183,11 +263,13 @@
                                                                               \
    private:                                                                   \
     enum class ColumnIndex : uint32_t {                                       \
-      id, /* Expands to col1, col2, ... */                                    \
+      id,                                                                     \
+      type, /* Expands to col1, col2, ... */                                  \
       PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_NAME_COMMA) kNumCols           \
     };                                                                        \
                                                                               \
     parent_class_name* parent_;                                               \
+                                                                              \
     /* Expands to                                                             \
      * SparseVector<col1_type> col1_;                                         \
      * SparseVector<col2_type> col2_;                                         \
diff --git a/src/trace_processor/tables/macros_unittest.cc b/src/trace_processor/tables/macros_unittest.cc
index 45f4119..c270aea 100644
--- a/src/trace_processor/tables/macros_unittest.cc
+++ b/src/trace_processor/tables/macros_unittest.cc
@@ -23,52 +23,68 @@
 namespace {
 
 #define PERFETTO_TP_TEST_EVENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestEventTable)                                    \
+  NAME(TestEventTable, "event")                           \
   PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
   C(int64_t, ts)                                          \
   C(int64_t, arg_set_id)
 PERFETTO_TP_TABLE(PERFETTO_TP_TEST_EVENT_TABLE_DEF);
 
 #define PERFETTO_TP_TEST_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestSliceTable)                                    \
+  NAME(TestSliceTable, "slice")                           \
   PARENT(PERFETTO_TP_TEST_EVENT_TABLE_DEF, C)             \
   C(base::Optional<int64_t>, dur)                         \
   C(int64_t, depth)
 PERFETTO_TP_TABLE(PERFETTO_TP_TEST_SLICE_TABLE_DEF);
 
 #define PERFETTO_TP_TEST_CPU_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestCpuSliceTable)                                     \
+  NAME(TestCpuSliceTable, "cpu_slice")                        \
   PARENT(PERFETTO_TP_TEST_SLICE_TABLE_DEF, C)                 \
   C(int64_t, cpu)                                             \
   C(int64_t, priority)                                        \
   C(StringPool::Id, end_state)
 PERFETTO_TP_TABLE(PERFETTO_TP_TEST_CPU_SLICE_TABLE_DEF);
 
+TEST(TableMacrosUnittest, Name) {
+  StringPool pool;
+  TestEventTable event(&pool, nullptr);
+  TestSliceTable slice(&pool, &event);
+  TestCpuSliceTable cpu_slice(&pool, &slice);
+
+  ASSERT_EQ(event.table_name(), "event");
+  ASSERT_EQ(slice.table_name(), "slice");
+  ASSERT_EQ(cpu_slice.table_name(), "cpu_slice");
+}
+
 TEST(TableMacrosUnittest, InsertParent) {
   StringPool pool;
   TestEventTable event(&pool, nullptr);
   TestSliceTable slice(&pool, &event);
 
-  uint32_t id = event.Insert(100, 0);
+  uint32_t id = event.Insert(TestEventTable::Row(100, 0));
   ASSERT_EQ(id, 0u);
+  ASSERT_EQ(event.type().GetString(0), "event");
   ASSERT_EQ(event.ts()[0], 100);
   ASSERT_EQ(event.arg_set_id()[0], 0);
 
-  id = slice.Insert(200, 123, 10, 0);
+  id = slice.Insert(TestSliceTable::Row(200, 123, 10, 0));
   ASSERT_EQ(id, 1u);
 
+  ASSERT_EQ(event.type().GetString(1), "slice");
   ASSERT_EQ(event.ts()[1], 200);
   ASSERT_EQ(event.arg_set_id()[1], 123);
+  ASSERT_EQ(slice.type().GetString(0), "slice");
   ASSERT_EQ(slice.ts()[0], 200);
   ASSERT_EQ(slice.arg_set_id()[0], 123);
   ASSERT_EQ(slice.dur()[0], 10);
   ASSERT_EQ(slice.depth()[0], 0);
 
-  id = slice.Insert(210, 456, base::nullopt, 0);
+  id = slice.Insert(TestSliceTable::Row(210, 456, base::nullopt, 0));
   ASSERT_EQ(id, 2u);
 
+  ASSERT_EQ(event.type().GetString(2), "slice");
   ASSERT_EQ(event.ts()[2], 210);
   ASSERT_EQ(event.arg_set_id()[2], 456);
+  ASSERT_EQ(slice.type().GetString(1), "slice");
   ASSERT_EQ(slice.ts()[1], 210);
   ASSERT_EQ(slice.arg_set_id()[1], 456);
   ASSERT_EQ(slice.dur()[1], base::nullopt);
@@ -81,27 +97,32 @@
   TestSliceTable slice(&pool, &event);
   TestCpuSliceTable cpu_slice(&pool, &slice);
 
-  event.Insert(100, 0);
-  slice.Insert(200, 123, 10, 0);
+  event.Insert(TestEventTable::Row(100, 0));
+  slice.Insert(TestSliceTable::Row(200, 123, 10, 0));
 
   auto reason = pool.InternString("R");
-  uint32_t id = cpu_slice.Insert(205, 456, 5, 1, 4, 1024, reason);
+  uint32_t id =
+      cpu_slice.Insert(TestCpuSliceTable::Row(205, 456, 5, 1, 4, 1024, reason));
   ASSERT_EQ(id, 2u);
+  ASSERT_EQ(event.type().GetString(2), "cpu_slice");
   ASSERT_EQ(event.ts()[2], 205);
   ASSERT_EQ(event.arg_set_id()[2], 456);
 
+  ASSERT_EQ(slice.type().GetString(1), "cpu_slice");
   ASSERT_EQ(slice.ts()[1], 205);
   ASSERT_EQ(slice.arg_set_id()[1], 456);
   ASSERT_EQ(slice.dur()[1], 5);
   ASSERT_EQ(slice.depth()[1], 1);
 
+  ASSERT_EQ(cpu_slice.type().GetString(0), "cpu_slice");
   ASSERT_EQ(cpu_slice.ts()[0], 205);
   ASSERT_EQ(cpu_slice.arg_set_id()[0], 456);
   ASSERT_EQ(cpu_slice.dur()[0], 5);
   ASSERT_EQ(cpu_slice.depth()[0], 1);
   ASSERT_EQ(cpu_slice.cpu()[0], 4);
   ASSERT_EQ(cpu_slice.priority()[0], 1024);
-  ASSERT_EQ(cpu_slice.end_state()[0], "R");
+  ASSERT_EQ(cpu_slice.end_state()[0], reason);
+  ASSERT_EQ(cpu_slice.end_state().GetString(0), "R");
 }
 
 }  // namespace
diff --git a/src/trace_processor/tables/slice_tables.h b/src/trace_processor/tables/slice_tables.h
index 2f6c5b8..57dc822 100644
--- a/src/trace_processor/tables/slice_tables.h
+++ b/src/trace_processor/tables/slice_tables.h
@@ -24,7 +24,7 @@
 namespace tables {
 
 #define PERFETTO_TP_GPU_SLICES_DEF(NAME, PARENT, C) \
-  NAME(GpuSliceTable)                               \
+  NAME(GpuSliceTable, "gpu_slice")                  \
   PERFETTO_TP_ROOT_TABLE(PARENT, C)                 \
   C(uint32_t, slice_id)                             \
   C(base::Optional<int64_t>, context_id)            \
diff --git a/src/trace_processor/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index 49469ba..16d398c 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -24,8 +24,15 @@
 namespace trace_processor {
 namespace tables {
 
+#define PERFETTO_TP_TRACK_TABLE_DEF(NAME, PARENT, C) \
+  NAME(TrackTable, "track")                          \
+  PERFETTO_TP_ROOT_TABLE(PARENT, C)                  \
+  C(StringPool::Id, name)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_TRACK_TABLE_DEF);
+
 #define PERFETTO_TP_GPU_TRACKS_DEF(NAME, PARENT, C) \
-  NAME(GpuTrackTable)                               \
+  NAME(GpuTrackTable, "gpu_track")                  \
   PERFETTO_TP_ROOT_TABLE(PARENT, C)                 \
   C(uint32_t, track_id)                             \
   C(StringPool::Id, scope)                          \
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 1d6a818..9f126ba 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -66,7 +66,6 @@
 #include "src/trace_processor/thread_table.h"
 #include "src/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/trace_sorter.h"
-#include "src/trace_processor/track_table.h"
 #include "src/trace_processor/virtual_track_tracker.h"
 #include "src/trace_processor/window_operator_table.h"
 
@@ -302,13 +301,15 @@
   StackProfileFrameTable::RegisterTable(*db_, context_.storage.get());
   StackProfileMappingTable::RegisterTable(*db_, context_.storage.get());
   MetadataTable::RegisterTable(*db_, context_.storage.get());
-  TrackTable::RegisterTable(*db_, context_.storage.get());
 
   // New style db-backed tables.
-  DbSqliteTable::RegisterTable(*db_, &context_.storage->gpu_slice_table(),
-                               "gpu_slice");
-  DbSqliteTable::RegisterTable(*db_, &context_.storage->gpu_track_table(),
-                               "gpu_track");
+  const TraceStorage* storage = context_.storage.get();
+  DbSqliteTable::RegisterTable(*db_, &storage->track_table(),
+                               storage->track_table().table_name());
+  DbSqliteTable::RegisterTable(*db_, &storage->gpu_slice_table(),
+                               storage->gpu_slice_table().table_name());
+  DbSqliteTable::RegisterTable(*db_, &storage->gpu_track_table(),
+                               storage->gpu_track_table().table_name());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() {
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 4c7515d..3a5455b 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -1172,8 +1172,8 @@
     return std::make_pair(table_id, row);
   }
 
-  const Tracks& tracks() const { return tracks_; }
-  Tracks* mutable_tracks() { return &tracks_; }
+  const tables::TrackTable& track_table() const { return track_table_; }
+  tables::TrackTable* mutable_track_table() { return &track_table_; }
 
   const VirtualTracks& virtual_tracks() const { return virtual_tracks_; }
   VirtualTracks* mutable_virtual_tracks() { return &virtual_tracks_; }
@@ -1308,7 +1308,7 @@
   Metadata metadata_{};
 
   // Metadata for tracks.
-  Tracks tracks_;
+  tables::TrackTable track_table_{&string_pool_, nullptr};
 
   // Metadata for virtual slice tracks.
   VirtualTracks virtual_tracks_;
diff --git a/src/trace_processor/track_table.cc b/src/trace_processor/track_table.cc
deleted file mode 100644
index 57b8564..0000000
--- a/src/trace_processor/track_table.cc
+++ /dev/null
@@ -1,48 +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/trace_processor/track_table.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-TrackTable::TrackTable(sqlite3*, const TraceStorage* storage)
-    : storage_(storage) {}
-
-void TrackTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<TrackTable>(db, storage, "track");
-}
-
-StorageSchema TrackTable::CreateStorageSchema() {
-  const auto& tracks = storage_->tracks();
-  return StorageSchema::Builder()
-      .AddGenericNumericColumn("id", RowAccessor())
-      .AddStringColumn("name", &tracks.names(), &storage_->string_pool())
-      .Build({"id"});
-}
-
-uint32_t TrackTable::RowCount() {
-  return storage_->tracks().track_count();
-}
-
-int TrackTable::BestIndex(const QueryConstraints& qc, BestIndexInfo* info) {
-  info->order_by_consumed = true;
-  info->estimated_cost = HasEqConstraint(qc, "id") ? 1 : RowCount();
-  return SQLITE_OK;
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/track_table.h b/src/trace_processor/track_table.h
deleted file mode 100644
index 7e77ec0..0000000
--- a/src/trace_processor/track_table.h
+++ /dev/null
@@ -1,43 +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_TRACK_TABLE_H_
-#define SRC_TRACE_PROCESSOR_TRACK_TABLE_H_
-
-#include "src/trace_processor/storage_table.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class TrackTable : public StorageTable {
- public:
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
-  TrackTable(sqlite3*, const TraceStorage*);
-
-  // StorageTable implementation.
-  StorageSchema CreateStorageSchema() override;
-  uint32_t RowCount() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-
- private:
-  const TraceStorage* const storage_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TRACK_TABLE_H_
diff --git a/src/trace_processor/virtual_track_tracker.cc b/src/trace_processor/virtual_track_tracker.cc
index 909539f..0cb4e08 100644
--- a/src/trace_processor/virtual_track_tracker.cc
+++ b/src/trace_processor/virtual_track_tracker.cc
@@ -31,7 +31,8 @@
   if (it != tracks_.end())
     return it->second;
 
-  TrackId track_id = context_->storage->mutable_tracks()->AddTrack(track_name);
+  TrackId track_id = context_->storage->mutable_track_table()->Insert(
+      tables::TrackTable::Row(track_name));
   context_->storage->mutable_virtual_tracks()->AddVirtualTrack(
       track_id, id_tuple.scope, id_tuple.upid);
   tracks_[id_tuple] = track_id;
diff --git a/src/trace_processor/wasm_bridge.cc b/src/trace_processor/wasm_bridge.cc
index 5bb9b3f..b5a1e58 100644
--- a/src/trace_processor/wasm_bridge.cc
+++ b/src/trace_processor/wasm_bridge.cc
@@ -22,7 +22,6 @@
 #include "perfetto/trace_processor/trace_processor.h"
 
 #include "protos/perfetto/trace_processor/raw_query.pb.h"
-#include "protos/perfetto/trace_processor/sched.pb.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -35,8 +34,7 @@
 // The function is generic and thankfully we need just one for all methods
 // because the output is always a protobuf buffer.
 // Args:
-//  RequestID: the ID passed by the embedder when invoking the RPC method (e.g.,
-//             the first argument passed to sched_getSchedEvents()).
+//  RequestID: the ID passed by the embedder when invoking the RPC method.
 using ReplyFunction = void (*)(RequestID,
                                bool success,
                                const char* /*proto_reply_data*/,
diff --git a/src/traced/probes/filesystem/file_scanner.cc b/src/traced/probes/filesystem/file_scanner.cc
index c71bbdf..17dd253 100644
--- a/src/traced/probes/filesystem/file_scanner.cc
+++ b/src/traced/probes/filesystem/file_scanner.cc
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
 #include "src/traced/probes/filesystem/inode_file_data_source.h"
 
 namespace perfetto {
diff --git a/src/traced/probes/filesystem/file_scanner.h b/src/traced/probes/filesystem/file_scanner.h
index 5fac41d..aee07db 100644
--- a/src/traced/probes/filesystem/file_scanner.h
+++ b/src/traced/probes/filesystem/file_scanner.h
@@ -34,7 +34,7 @@
     virtual bool OnInodeFound(BlockDeviceID,
                               Inode,
                               const std::string&,
-                              protos::pbzero::InodeFileMap_Entry_Type) = 0;
+                              InodeFileMap_Entry_Type) = 0;
     virtual void OnInodeScanDone() = 0;
     virtual ~Delegate();
   };
diff --git a/src/traced/probes/filesystem/file_scanner_unittest.cc b/src/traced/probes/filesystem/file_scanner_unittest.cc
index a035c56..59b2d95 100644
--- a/src/traced/probes/filesystem/file_scanner_unittest.cc
+++ b/src/traced/probes/filesystem/file_scanner_unittest.cc
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "perfetto/base/logging.h"
+#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
 #include "src/base/test/test_task_runner.h"
 #include "test/gtest_and_gmock.h"
 
@@ -33,28 +34,25 @@
 
 class TestDelegate : public FileScanner::Delegate {
  public:
-  TestDelegate(
-      std::function<bool(BlockDeviceID,
-                         Inode,
-                         const std::string&,
-                         protos::pbzero::InodeFileMap_Entry_Type)> callback,
-      std::function<void()> done_callback)
+  TestDelegate(std::function<bool(BlockDeviceID,
+                                  Inode,
+                                  const std::string&,
+                                  InodeFileMap_Entry_Type)> callback,
+               std::function<void()> done_callback)
       : callback_(std::move(callback)),
         done_callback_(std::move(done_callback)) {}
   bool OnInodeFound(BlockDeviceID block_device_id,
                     Inode inode,
                     const std::string& path,
-                    protos::pbzero::InodeFileMap_Entry_Type type) override {
+                    InodeFileMap_Entry_Type type) override {
     return callback_(block_device_id, inode, path, type);
   }
 
   void OnInodeScanDone() { return done_callback_(); }
 
  private:
-  std::function<bool(BlockDeviceID,
-                     Inode,
-                     const std::string&,
-                     protos::pbzero::InodeFileMap_Entry_Type)>
+  std::function<
+      bool(BlockDeviceID, Inode, const std::string&, InodeFileMap_Entry_Type)>
       callback_;
   std::function<void()> done_callback_;
 };
@@ -63,7 +61,7 @@
   FileEntry(BlockDeviceID block_device_id,
             Inode inode,
             std::string path,
-            protos::pbzero::InodeFileMap_Entry_Type type)
+            InodeFileMap_Entry_Type type)
       : block_device_id_(block_device_id),
         inode_(inode),
         path_(std::move(path)),
@@ -78,7 +76,7 @@
   BlockDeviceID block_device_id_;
   Inode inode_;
   std::string path_;
-  protos::pbzero::InodeFileMap_Entry_Type type_;
+  InodeFileMap_Entry_Type type_;
 };
 
 struct stat CheckStat(const std::string& path) {
@@ -87,8 +85,7 @@
   return buf;
 }
 
-FileEntry StatFileEntry(const std::string& path,
-                        protos::pbzero::InodeFileMap_Entry_Type type) {
+FileEntry StatFileEntry(const std::string& path, InodeFileMap_Entry_Type type) {
   struct stat buf = CheckStat(path);
   return FileEntry(buf.st_dev, buf.st_ino, path, type);
 }
@@ -98,7 +95,7 @@
   bool done = false;
   TestDelegate delegate(
       [&seen](BlockDeviceID, Inode, const std::string&,
-              protos::pbzero::InodeFileMap_Entry_Type) {
+              InodeFileMap_Entry_Type) {
         ++seen;
         return false;
       },
@@ -116,7 +113,7 @@
   base::TestTaskRunner task_runner;
   TestDelegate delegate(
       [&seen](BlockDeviceID, Inode, const std::string&,
-              protos::pbzero::InodeFileMap_Entry_Type) {
+              InodeFileMap_Entry_Type) {
         ++seen;
         return false;
       },
@@ -134,8 +131,7 @@
   std::vector<FileEntry> file_entries;
   TestDelegate delegate(
       [&file_entries](BlockDeviceID block_device_id, Inode inode,
-                      const std::string& path,
-                      protos::pbzero::InodeFileMap_Entry_Type type) {
+                      const std::string& path, InodeFileMap_Entry_Type type) {
         file_entries.emplace_back(block_device_id, inode, path, type);
         return true;
       },
@@ -161,8 +157,7 @@
   std::vector<FileEntry> file_entries;
   TestDelegate delegate(
       [&file_entries](BlockDeviceID block_device_id, Inode inode,
-                      const std::string& path,
-                      protos::pbzero::InodeFileMap_Entry_Type type) {
+                      const std::string& path, InodeFileMap_Entry_Type type) {
         file_entries.emplace_back(block_device_id, inode, path, type);
         return true;
       },
diff --git a/src/traced/probes/filesystem/inode_file_data_source.cc b/src/traced/probes/filesystem/inode_file_data_source.cc
index 9446c8c..d4643cb 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source.cc
@@ -29,6 +29,7 @@
 #include "perfetto/ext/tracing/core/trace_writer.h"
 
 #include "protos/perfetto/config/inode_file/inode_file_config.pbzero.h"
+#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/traced/probes/filesystem/file_scanner.h"
 
@@ -65,7 +66,7 @@
   bool OnInodeFound(BlockDeviceID block_device_id,
                     Inode inode_number,
                     const std::string& path,
-                    protos::pbzero::InodeFileMap_Entry_Type type) {
+                    InodeFileMap_Entry_Type type) {
     std::unordered_map<Inode, InodeMapValue>& inode_map =
         (*map_)[block_device_id];
     inode_map[inode_number].SetType(type);
@@ -95,7 +96,8 @@
                                          const InodeMapValue& inode_map_value) {
   auto* entry = destination->add_entries();
   entry->set_inode_number(inode_number);
-  entry->set_type(inode_map_value.type());
+  entry->set_type(static_cast<protos::pbzero::InodeFileMap_Entry_Type>(
+      inode_map_value.type()));
   for (const auto& path : inode_map_value.paths())
     entry->add_paths(path.c_str());
 }
@@ -293,11 +295,10 @@
   it->second.erase(inode_number);
 }
 
-bool InodeFileDataSource::OnInodeFound(
-    BlockDeviceID block_device_id,
-    Inode inode_number,
-    const std::string& path,
-    protos::pbzero::InodeFileMap_Entry_Type type) {
+bool InodeFileDataSource::OnInodeFound(BlockDeviceID block_device_id,
+                                       Inode inode_number,
+                                       const std::string& path,
+                                       InodeFileMap_Entry_Type type) {
   auto it = missing_inodes_.find(block_device_id);
   if (it == missing_inodes_.end())
     return true;
diff --git a/src/traced/probes/filesystem/inode_file_data_source.h b/src/traced/probes/filesystem/inode_file_data_source.h
index 4b7d3fe..529fe2c 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.h
+++ b/src/traced/probes/filesystem/inode_file_data_source.h
@@ -99,7 +99,7 @@
   bool OnInodeFound(BlockDeviceID block_device_id,
                     Inode inode_number,
                     const std::string& path,
-                    protos::pbzero::InodeFileMap_Entry_Type type) override;
+                    InodeFileMap_Entry_Type type) override;
   void OnInodeScanDone() override;
 
   void AddRootsForBlockDevice(BlockDeviceID block_device_id,
diff --git a/src/traced/probes/filesystem/lru_inode_cache_unittest.cc b/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
index a71a584..86aed74 100644
--- a/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
+++ b/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
@@ -19,6 +19,7 @@
 #include <string>
 #include <tuple>
 
+#include "protos/perfetto/trace/filesystem/inode_file_map.pbzero.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 8e2e696..657a459 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -2481,48 +2481,6 @@
   {
     events.emplace_back(Event{});
     Event* event = &events.back();
-    event->name = "gpu_sched_enqueue";
-    event->group = "gpu";
-    event->proto_field_id = 333;
-    event->fields.push_back(MakeField("ctx_id", 1, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("job_id", 2, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("priority", 3, ProtoSchemaType::kUint32));
-    event->fields.push_back(
-        MakeField("submission_id", 4, ProtoSchemaType::kUint32));
-  }
-
-  {
-    events.emplace_back(Event{});
-    Event* event = &events.back();
-    event->name = "gpu_sched_submit";
-    event->group = "gpu";
-    event->proto_field_id = 334;
-    event->fields.push_back(MakeField("ctx_id", 1, ProtoSchemaType::kUint32));
-    event->fields.push_back(
-        MakeField("hwqueue_id", 2, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("job_id", 3, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("priority", 4, ProtoSchemaType::kUint32));
-    event->fields.push_back(
-        MakeField("submission_id", 5, ProtoSchemaType::kUint32));
-  }
-
-  {
-    events.emplace_back(Event{});
-    Event* event = &events.back();
-    event->name = "gpu_sched_complete";
-    event->group = "gpu";
-    event->proto_field_id = 335;
-    event->fields.push_back(MakeField("ctx_id", 1, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("job_id", 2, ProtoSchemaType::kUint32));
-    event->fields.push_back(MakeField("msg", 3, ProtoSchemaType::kString));
-    event->fields.push_back(MakeField("priority", 4, ProtoSchemaType::kUint32));
-    event->fields.push_back(
-        MakeField("submission_id", 5, ProtoSchemaType::kUint32));
-  }
-
-  {
-    events.emplace_back(Event{});
-    Event* event = &events.back();
     event->name = "i2c_read";
     event->group = "i2c";
     event->proto_field_id = 27;
diff --git a/src/traced/probes/ftrace/test/data/synthetic/available_events b/src/traced/probes/ftrace/test/data/synthetic/available_events
index 94a6085..0a0ea6f 100644
--- a/src/traced/probes/ftrace/test/data/synthetic/available_events
+++ b/src/traced/probes/ftrace/test/data/synthetic/available_events
@@ -5,6 +5,3 @@
 clk:clk_enable
 clk:clk_disable
 clk:clk_set_rate
-gpu:gpu_sched_enqueue
-gpu:gpu_sched_submit
-gpu:gpu_sched_complete
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_complete/format b/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_complete/format
deleted file mode 100644
index 0027729..0000000
--- a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_complete/format
+++ /dev/null
@@ -1,15 +0,0 @@
-name: gpu_sched_complete
-ID: 176
-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:uint32_t ctx_id;	offset:8;	size:4;	signed:0;
-	field:uint32_t submission_id;	offset:12;	size:4;	signed:0;
-	field:uint32_t job_id;	offset:16;	size:4;	signed:0;
-	field:uint32_t priority;	offset:20;	size:4;	signed:0;
-	field:__data_loc char[] msg;	offset:24;	size:4;	signed:0;
-
-print fmt: "ctx_id=%u submission_id=%u job_id=%u priority=%u, msg=%s", REC->ctx_id, REC->submission_id, REC->job_id, REC->priority, __get_str(msg)
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_enqueue/format b/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_enqueue/format
deleted file mode 100644
index b220569..0000000
--- a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_enqueue/format
+++ /dev/null
@@ -1,14 +0,0 @@
-name: gpu_sched_enqueue
-ID: 174
-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:uint32_t ctx_id;	offset:8;	size:4;	signed:0;
-	field:uint32_t submission_id;	offset:12;	size:4;	signed:0;
-	field:uint32_t job_id;	offset:16;	size:4;	signed:0;
-	field:uint32_t priority;	offset:20;	size:4;	signed:0;
-
-print fmt: "ctx_id=%u submission_id=%u job_id=%u priority=%u", REC->ctx_id, REC->submission_id, REC->job_id, REC->priority
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_submit/format b/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_submit/format
deleted file mode 100644
index 55a4e62..0000000
--- a/src/traced/probes/ftrace/test/data/synthetic/events/gpu/gpu_sched_submit/format
+++ /dev/null
@@ -1,15 +0,0 @@
-name: gpu_sched_submit
-ID: 175
-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:uint32_t ctx_id;	offset:8;	size:4;	signed:0;
-	field:uint32_t submission_id;	offset:12;	size:4;	signed:0;
-	field:uint32_t job_id;	offset:16;	size:4;	signed:0;
-	field:uint32_t priority;	offset:20;	size:4;	signed:0;
-	field:uint32_t hwqueue_id;	offset:24;	size:4;	signed:0;
-
-print fmt: "ctx_id=%u submission_id=%u job_id=%u priority=%u hwqueue_id=%u", REC->ctx_id, REC->submission_id, REC->job_id, REC->priority, REC->hwqueue_id
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index a194748..4a13c32 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -55,7 +55,6 @@
     "core/shared_memory_arbiter_impl.cc",
     "core/shared_memory_arbiter_impl.h",
     "core/sliced_protobuf_input_stream.cc",
-    "core/sliced_protobuf_input_stream.h",
     "core/startup_trace_writer.cc",
     "core/startup_trace_writer_registry.cc",
     "core/test_config.cc",
@@ -179,7 +178,7 @@
 if (enable_perfetto_ipc) {
   # Posix specialization of the tracing library for Linux / Android / Mac.
   # Provides an IPC transport over a UNIX domain socket.
-  static_library("ipc") {
+  source_set("ipc") {
     public_deps = [
       "../../include/perfetto/ext/tracing/core",
       "../../include/perfetto/ext/tracing/ipc",
@@ -259,7 +258,7 @@
   ]
 
   if (enable_perfetto_ipc) {
-    deps += [ "../tracing:ipc" ]
+    deps += [ ":ipc" ]
     sources += [
       "internal/system_tracing_backend.cc",
       "internal/system_tracing_backend.h",
@@ -287,3 +286,20 @@
     ]
   }
 }
+
+if (enable_perfetto_benchmarks) {
+  source_set("benchmarks") {
+    testonly = true
+    deps = [
+      ":tracing",
+      "../../../../gn:benchmark",
+      "../../../../gn:default_deps",
+      "../../protos/perfetto/trace:zero",
+      "../../protos/perfetto/trace/ftrace:zero",
+      "../protozero",
+    ]
+    sources = [
+      "core/packet_stream_validator_benchmark.cc",
+    ]
+  }
+}
diff --git a/src/tracing/core/packet_stream_validator.h b/src/tracing/core/packet_stream_validator.h
index 8494f4e..4b1bdf3 100644
--- a/src/tracing/core/packet_stream_validator.h
+++ b/src/tracing/core/packet_stream_validator.h
@@ -17,7 +17,7 @@
 #ifndef SRC_TRACING_CORE_PACKET_STREAM_VALIDATOR_H_
 #define SRC_TRACING_CORE_PACKET_STREAM_VALIDATOR_H_
 
-#include "src/tracing/core/sliced_protobuf_input_stream.h"
+#include "perfetto/ext/tracing/core/sliced_protobuf_input_stream.h"
 
 namespace perfetto {
 
diff --git a/src/tracing/core/packet_stream_validator_benchmark.cc b/src/tracing/core/packet_stream_validator_benchmark.cc
new file mode 100644
index 0000000..9813b61
--- /dev/null
+++ b/src/tracing/core/packet_stream_validator_benchmark.cc
@@ -0,0 +1,73 @@
+// 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 <benchmark/benchmark.h>
+
+#include "src/tracing/core/packet_stream_validator.h"
+
+#include "perfetto/ext/tracing/core/slice.h"
+#include "perfetto/protozero/scattered_heap_buffer.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/sched.pbzero.h"
+#include "protos/perfetto/trace/test_event.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace {
+
+static void BM_PacketStreamValidator(benchmark::State& state) {
+  using namespace perfetto;
+
+  // Create a packet that resembles a ftrace sched bundle. A ftrace page is
+  // 4KB and typically contains ~64 sched events of 64 bytes each.
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet;
+  auto* bundle = packet->set_ftrace_events();
+  bundle->set_cpu(1);
+  for (size_t events = 0; events < 64; events++) {
+    auto* ftrace_evt = bundle->add_event();
+    ftrace_evt->set_pid(12345);
+    ftrace_evt->set_timestamp(1000ull * 1000 * 1000 * 3600 * 24 * 365);
+    auto* sched_switch = ftrace_evt->set_sched_switch();
+    sched_switch->set_prev_comm("thread_name_1");
+    sched_switch->set_prev_pid(12345);
+    sched_switch->set_prev_state(42);
+    sched_switch->set_next_comm("thread_name_2");
+    sched_switch->set_next_pid(67890);
+  }
+  std::vector<uint8_t> buf = packet.SerializeAsArray();
+
+  // Append 10 packets like the one above, splitting each packet into slices
+  // of 512B each.
+  Slices slices;
+  static constexpr size_t kSliceSize = 512;
+  for (size_t num_packets = 0; num_packets < 10; num_packets++) {
+    for (size_t pos = 0; pos < buf.size(); pos += kSliceSize) {
+      size_t slice_size = std::min(kSliceSize, buf.size() - pos);
+      Slice slice = Slice::Allocate(slice_size);
+      memcpy(slice.own_data(), &buf[pos], slice_size);
+      slices.emplace_back(std::move(slice));
+    }
+  }
+
+  bool res = true;
+  while (state.KeepRunning()) {
+    res &= PacketStreamValidator::Validate(slices);
+  }
+  PERFETTO_CHECK(res);
+}
+
+}  // namespace
+
+BENCHMARK(BM_PacketStreamValidator);
diff --git a/src/tracing/core/sliced_protobuf_input_stream.cc b/src/tracing/core/sliced_protobuf_input_stream.cc
index 65b0bb1..3301e63 100644
--- a/src/tracing/core/sliced_protobuf_input_stream.cc
+++ b/src/tracing/core/sliced_protobuf_input_stream.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/tracing/core/sliced_protobuf_input_stream.h"
+#include "perfetto/ext/tracing/core/sliced_protobuf_input_stream.h"
 
 #include <algorithm>
 
@@ -86,17 +86,17 @@
   return true;
 }
 
-google::protobuf::int64 SlicedProtobufInputStream::ByteCount() const {
+SlicedProtobufInputStream::int64 SlicedProtobufInputStream::ByteCount() const {
   PERFETTO_DCHECK(Validate());
-  google::protobuf::int64 count = 0;
+  int64_t count = 0;
   for (auto it = slices_->begin(); it != slices_->end(); it++) {
     if (it == cur_slice_) {
-      count += static_cast<google::protobuf::int64>(pos_in_cur_slice_);
+      count += static_cast<int64_t>(pos_in_cur_slice_);
       break;
     }
-    count += static_cast<google::protobuf::int64>(it->size);
+    count += static_cast<int64_t>(it->size);
   }
-  return count;
+  return static_cast<SlicedProtobufInputStream::int64>(count);
 }
 
 bool SlicedProtobufInputStream::Validate() const {
diff --git a/src/tracing/core/sliced_protobuf_input_stream.h b/src/tracing/core/sliced_protobuf_input_stream.h
deleted file mode 100644
index 9e80966..0000000
--- a/src/tracing/core/sliced_protobuf_input_stream.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef SRC_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
-#define SRC_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
-
-#include "perfetto/ext/tracing/core/slice.h"
-
-#include <stdint.h>
-
-#include <google/protobuf/io/zero_copy_stream.h>
-
-namespace perfetto {
-
-// Wraps a sequence of Slice(s) in a protobuf ZeroCopyInputStream that can be
-// passed to protobuf::Message::ParseFromZeroCopyStream().
-class SlicedProtobufInputStream
-    : public google::protobuf::io::ZeroCopyInputStream {
- public:
-  explicit SlicedProtobufInputStream(const Slices*);
-  ~SlicedProtobufInputStream() override;
-
-  // ZeroCopyInputStream implementation. See zero_copy_stream.h for the API
-  // contract of the methods below.
-  bool Next(const void** data, int* size) override;
-  void BackUp(int count) override;
-  bool Skip(int count) override;
-  google::protobuf::int64 ByteCount() const override;
-
- private:
-  bool Validate() const;
-
-  const Slices* const slices_;
-  Slices::const_iterator cur_slice_;
-  size_t pos_in_cur_slice_ = 0;
-};
-
-}  // namespace perfetto
-
-#endif  // SRC_TRACING_CORE_SLICED_PROTOBUF_INPUT_STREAM_H_
diff --git a/src/tracing/core/sliced_protobuf_input_stream_unittest.cc b/src/tracing/core/sliced_protobuf_input_stream_unittest.cc
index 59f52f2..fc0c075 100644
--- a/src/tracing/core/sliced_protobuf_input_stream_unittest.cc
+++ b/src/tracing/core/sliced_protobuf_input_stream_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/tracing/core/sliced_protobuf_input_stream.h"
+#include "perfetto/ext/tracing/core/sliced_protobuf_input_stream.h"
 
 #include "perfetto/ext/base/utils.h"
 #include "test/gtest_and_gmock.h"
diff --git a/src/tracing/core/startup_trace_writer_unittest.cc b/src/tracing/core/startup_trace_writer_unittest.cc
index 139456e..a7cd419 100644
--- a/src/tracing/core/startup_trace_writer_unittest.cc
+++ b/src/tracing/core/startup_trace_writer_unittest.cc
@@ -23,7 +23,6 @@
 #include "src/base/test/test_task_runner.h"
 #include "src/tracing/core/patch_list.h"
 #include "src/tracing/core/shared_memory_arbiter_impl.h"
-#include "src/tracing/core/sliced_protobuf_input_stream.h"
 #include "src/tracing/core/trace_buffer.h"
 #include "src/tracing/test/aligned_buffer_test.h"
 #include "src/tracing/test/fake_producer_endpoint.h"
@@ -147,15 +146,10 @@
       EXPECT_EQ(static_cast<uid_t>(1),
                 sequence_properties.producer_uid_trusted);
 
-      SlicedProtobufInputStream stream(&packet.slices());
-      size_t size = 0;
-      for (const Slice& slice : packet.slices())
-        size += slice.size;
       protos::TracePacket parsed_packet;
-      bool success = parsed_packet.ParseFromBoundedZeroCopyStream(
-          &stream, static_cast<int>(size));
-      EXPECT_TRUE(success);
-      if (!success)
+      bool res = parsed_packet.ParseFromString(packet.GetRawBytesForTesting());
+      EXPECT_TRUE(res);
+      if (!res)
         break;
 
       // If the buffer size was exceeded, the data loss packet should be the
diff --git a/src/tracing/core/trace_packet.cc b/src/tracing/core/trace_packet.cc
index c3891c5..0dea150 100644
--- a/src/tracing/core/trace_packet.cc
+++ b/src/tracing/core/trace_packet.cc
@@ -18,7 +18,6 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/protozero/proto_utils.h"
-#include "src/tracing/core/sliced_protobuf_input_stream.h"
 
 namespace perfetto {
 
@@ -63,10 +62,16 @@
   return std::make_tuple(&preamble_[0], preamble_size);
 }
 
-std::unique_ptr<TracePacket::ZeroCopyInputStream>
-TracePacket::CreateSlicedInputStream() const {
-  return std::unique_ptr<ZeroCopyInputStream>(
-      new SlicedProtobufInputStream(&slices_));
+std::string TracePacket::GetRawBytesForTesting() {
+  std::string data;
+  data.resize(size());
+  size_t pos = 0;
+  for (const Slice& slice : slices()) {
+    PERFETTO_CHECK(pos + slice.size <= data.size());
+    memcpy(&data[pos], slice.start, slice.size);
+    pos += slice.size;
+  }
+  return data;
 }
 
 }  // namespace perfetto
diff --git a/src/tracing/core/trace_packet_unittest.cc b/src/tracing/core/trace_packet_unittest.cc
index f2aaa45..0469ae8 100644
--- a/src/tracing/core/trace_packet_unittest.cc
+++ b/src/tracing/core/trace_packet_unittest.cc
@@ -63,7 +63,7 @@
   ASSERT_EQ(tp.slices().end(), ++slice);
 
   protos::TracePacket decoded_packet;
-  ASSERT_TRUE(tp.Decode(&decoded_packet));
+  ASSERT_TRUE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
   ASSERT_EQ(proto.for_testing().str(), decoded_packet.for_testing().str());
 }
 
@@ -94,7 +94,7 @@
   ASSERT_EQ(tp.slices().end(), ++slice);
 
   protos::TracePacket decoded_packet;
-  ASSERT_TRUE(tp.Decode(&decoded_packet));
+  ASSERT_TRUE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
   ASSERT_EQ(proto.for_testing().str(), decoded_packet.for_testing().str());
 }
 
@@ -105,7 +105,7 @@
   TracePacket tp;
   tp.AddSlice({ser_buf.data(), ser_buf.size() - 2});  // corrupted.
   protos::TracePacket decoded_packet;
-  ASSERT_FALSE(tp.Decode(&decoded_packet));
+  ASSERT_FALSE(decoded_packet.ParseFromString(tp.GetRawBytesForTesting()));
 }
 
 // Tests that the GetProtoPreamble() logic returns a valid preamble that allows
diff --git a/src/tracing/test/mock_consumer.cc b/src/tracing/test/mock_consumer.cc
index 867d98c..17a983d 100644
--- a/src/tracing/test/mock_consumer.cc
+++ b/src/tracing/test/mock_consumer.cc
@@ -109,7 +109,7 @@
             for (TracePacket& packet : *packets) {
               decoded_packets.emplace_back();
               protos::TracePacket* decoded_packet = &decoded_packets.back();
-              packet.Decode(decoded_packet);
+              decoded_packet->ParseFromString(packet.GetRawBytesForTesting());
             }
             if (!has_more)
               on_read_buffers();
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 56cc2c9..799b614 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -308,7 +308,8 @@
 
             for (auto& encoded_packet : *packets) {
               protos::TracePacket packet;
-              ASSERT_TRUE(encoded_packet.Decode(&packet));
+              ASSERT_TRUE(packet.ParseFromString(
+                  encoded_packet.GetRawBytesForTesting()));
               if (packet.has_for_testing()) {
                 char buf[8];
                 sprintf(buf, "evt_%zu", num_pack_rx++);
@@ -516,7 +517,8 @@
                      std::vector<TracePacket>* packets, bool has_more) {
             for (auto& encoded_packet : *packets) {
               protos::TracePacket packet;
-              ASSERT_TRUE(encoded_packet.Decode(&packet));
+              ASSERT_TRUE(packet.ParseFromString(
+                  encoded_packet.GetRawBytesForTesting()));
               if (packet.has_for_testing()) {
                 num_test_pack_rx++;
               }
diff --git a/test/cts/Android.bp b/test/cts/Android.bp
index c7cee36..69fe21a 100644
--- a/test/cts/Android.bp
+++ b/test/cts/Android.bp
@@ -8,7 +8,7 @@
   static_libs: [
     "libgmock",
     "libprotobuf-cpp-lite",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
     "perfetto_trace_protos",
   ],
   shared_libs: [
diff --git a/test/cts/producer/jni/Android.bp b/test/cts/producer/jni/Android.bp
index 939a37b..b97ecdb 100644
--- a/test/cts/producer/jni/Android.bp
+++ b/test/cts/producer/jni/Android.bp
@@ -7,7 +7,7 @@
     "libgtest",
     "libprotobuf-cpp-lite",
     "perfetto_cts_jni_deps",
-    "perfetto_src_tracing_ipc",
+    "libperfetto_client_experimental",
   ],
   shared_libs: [
     "libandroid",
diff --git a/test/metrics/android_startup_cpu.out b/test/metrics/android_startup_cpu.out
index 0f7b080..2654ba5 100644
--- a/test/metrics/android_startup_cpu.out
+++ b/test/metrics/android_startup_cpu.out
@@ -5,22 +5,25 @@
           name : "Process1"
           cpu {
             id: 0
-            max_freq_khz: 5
-            min_freq_khz: 5
-            avg_freq_khz: 5
+            max_freq_khz: 500000
+            min_freq_khz: 500000
+            avg_freq_khz: 500000
             duration_ns: 2
           }
+          normalized_cpu_cycles: 1
         }
         threads {
           name: "p1-t2"
           cpu {
             id: 0
-            max_freq_khz: 14
-            min_freq_khz: 14
-            avg_freq_khz: 14
+            max_freq_khz: 1400000
+            min_freq_khz: 1400000
+            avg_freq_khz: 1400000
             duration_ns: 1
           }
+          normalized_cpu_cycles: 1
         }
+        normalized_cpu_cycles: 2
       }
       process_info {
         name: "Process2"
@@ -28,32 +31,36 @@
           name: "p2-t2"
           cpu {
             id: 1
-            max_freq_khz: 20
-            min_freq_khz: 20
-            avg_freq_khz: 20
+            max_freq_khz: 2000000
+            min_freq_khz: 2000000
+            avg_freq_khz: 2000000
             duration_ns: 2
           }
+          normalized_cpu_cycles: 4
         }
         threads {
           name: "p2-t3"
           cpu {
             id: 1
-            max_freq_khz: 80
-            min_freq_khz: 20
-            avg_freq_khz: 40
+            max_freq_khz: 8000000
+            min_freq_khz: 2000000
+            avg_freq_khz: 4000000
             duration_ns: 3
           }
+          normalized_cpu_cycles: 12
         }
         threads {
           name: "Process2"
           cpu {
             id: 0
-            max_freq_khz: 25
-            min_freq_khz: 25
-            avg_freq_khz: 25
+            max_freq_khz: 2500000
+            min_freq_khz: 2500000
+            avg_freq_khz: 2500000
             duration_ns: 2
           }
+          normalized_cpu_cycles: 5
         }
+        normalized_cpu_cycles: 21
       }
       process_info {
         name: "Process3"
@@ -61,18 +68,20 @@
           name: "Process3"
           cpu {
             id: 0
-            max_freq_khz: 14
-            min_freq_khz: 5
-            avg_freq_khz: 7
+            max_freq_khz: 1400000
+            min_freq_khz: 500000
+            avg_freq_khz: 725000
             duration_ns: 4
           }
           cpu {
             id: 1
-            max_freq_khz: 80
-            min_freq_khz: 80
-            avg_freq_khz: 80
+            max_freq_khz: 8000000
+            min_freq_khz: 8000000
+            avg_freq_khz: 8000000
             duration_ns: 2
           }
+          normalized_cpu_cycles: 18
         }
+        normalized_cpu_cycles: 18
       }
-}
\ No newline at end of file
+}
diff --git a/test/metrics/android_startup_cpu.py b/test/metrics/android_startup_cpu.py
index 044df5f..e450767 100644
--- a/test/metrics/android_startup_cpu.py
+++ b/test/metrics/android_startup_cpu.py
@@ -22,13 +22,13 @@
 trace.add_ftrace_packet(cpu=0)
 
 # CPU counters for CPU 0.
-trace.add_cpufreq(ts=9, freq=5, cpu=0)
-trace.add_cpufreq(ts=15, freq=14, cpu=0)
-trace.add_cpufreq(ts=17, freq=25, cpu=0)
+trace.add_cpufreq(ts=9, freq=500000, cpu=0)
+trace.add_cpufreq(ts=15, freq=1400000, cpu=0)
+trace.add_cpufreq(ts=17, freq=2500000, cpu=0)
 
 # CPU counters for CPU 1.
-trace.add_cpufreq(ts=11, freq=20, cpu=1)
-trace.add_cpufreq(ts=15, freq=80, cpu=1)
+trace.add_cpufreq(ts=11, freq=2000000, cpu=1)
+trace.add_cpufreq(ts=15, freq=8000000, cpu=1)
 
 # Add 3 processes. This also adds one main thread per process.
 trace.add_process_tree_packet()
diff --git a/test/test_helper.cc b/test/test_helper.cc
index e4af816..57e8b10 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -61,7 +61,8 @@
 void TestHelper::OnTraceData(std::vector<TracePacket> packets, bool has_more) {
   for (auto& encoded_packet : packets) {
     protos::TracePacket packet;
-    PERFETTO_CHECK(encoded_packet.Decode(&packet));
+    PERFETTO_CHECK(
+        packet.ParseFromString(encoded_packet.GetRawBytesForTesting()));
     if (packet.has_clock_snapshot() || packet.has_trace_config() ||
         packet.has_trace_stats() || !packet.synchronization_marker().empty() ||
         packet.has_system_info()) {
diff --git a/test/trace_processor/filter_row_vector.sql b/test/trace_processor/filter_row_vector.sql
new file mode 100644
index 0000000..d793cde
--- /dev/null
+++ b/test/trace_processor/filter_row_vector.sql
@@ -0,0 +1,6 @@
+SELECT ts FROM counter_values
+WHERE
+  ts > 72563651549 AND
+  counter_id = 7 AND
+  value != 17952.000000
+LIMIT 20
diff --git a/test/trace_processor/filter_row_vector_example_android_trace_30s.out b/test/trace_processor/filter_row_vector_example_android_trace_30s.out
new file mode 100644
index 0000000..937091d
--- /dev/null
+++ b/test/trace_processor/filter_row_vector_example_android_trace_30s.out
@@ -0,0 +1,21 @@
+"ts"
+72688754739
+72688833280
+72688842812
+72688849478
+72688854530
+72688862499
+72688866770
+72688871301
+72706000522
+72706468074
+72706827293
+72706865678
+72706939220
+72707490886
+72707510418
+72707557814
+72707569480
+72707698647
+72707767501
+72722626305
diff --git a/test/trace_processor/index b/test/trace_processor/index
index da0f0cc..41ef5c2 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -99,6 +99,7 @@
 counters_where_cpu.py counters_where_cpu.sql counters_where_cpu_counters_where_cpu.out
 counters_group_by_freq.py counters_group_by_freq.sql counters_group_by_freq_counters_group_by_freq.out
 counters_order_ref.py counters_order_ref.sql counters_order_ref_counters_order_ref.out
+../data/example_android_trace_30s.pb filter_row_vector.sql filter_row_vector_example_android_trace_30s.out
 
 # Null printing
 synth_1.py nulls.sql nulls.out
diff --git a/tools/ftrace_proto_gen/event_whitelist b/tools/ftrace_proto_gen/event_whitelist
index f0d5505..e0662ce 100644
--- a/tools/ftrace_proto_gen/event_whitelist
+++ b/tools/ftrace_proto_gen/event_whitelist
@@ -327,6 +327,3 @@
 raw_syscalls/sys_exit
 systrace/0
 power/gpu_frequency
-gpu/gpu_sched_enqueue
-gpu/gpu_sched_submit
-gpu/gpu_sched_complete
diff --git a/tools/ftrace_proto_gen/proto_gen_utils.cc b/tools/ftrace_proto_gen/proto_gen_utils.cc
index 79f0ea8..6fa9e3f 100644
--- a/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -91,9 +91,10 @@
 
 }  // namespace
 
+using base::Contains;
 using base::EndsWith;
 using base::StartsWith;
-using base::Contains;
+using base::Uppercase;
 
 VerifyStream::VerifyStream(std::string filename)
     : filename_(std::move(filename)) {
@@ -146,7 +147,7 @@
     }
     if (upperCaseNextChar) {
       upperCaseNextChar = false;
-      c = static_cast<char>(toupper(c));
+      c = Uppercase(c);
     }
     result.push_back(c);
   }
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 0d6b01a..05a7d50 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -61,11 +61,6 @@
     '//tools/trace_to_text:trace_to_text',
 ]
 
-# Targets which are testonly but should still be a cc_binary.
-non_test_binaries = [
-    '//src/trace_processor:trace_processor_shell',
-]
-
 # Defines a custom init_rc argument to be applied to the corresponding output
 # blueprint target.
 target_initrc = {
@@ -75,11 +70,11 @@
 
 target_host_supported = [
     '//protos/perfetto/trace:perfetto_trace_protos',
-    '//src/trace_processor:trace_processor_shell',
 ]
 
 target_host_only = [
     '//tools/trace_to_text:trace_to_text',
+    '//src/trace_processor:trace_processor_shell',
 ]
 
 # All module names are prefixed with this string to avoid collisions.
@@ -141,23 +136,12 @@
 def enable_gmock(module):
     module.static_libs.append('libgmock')
 
-
-def enable_gtest_prod(module):
-    module.static_libs.append('libgtest_prod')
-
-
-def enable_gtest(module):
-    assert module.type == 'cc_test'
-
-
 def enable_protobuf_full(module):
     module.shared_libs.append('libprotobuf-cpp-full')
 
-
 def enable_protobuf_lite(module):
     module.shared_libs.append('libprotobuf-cpp-lite')
 
-
 def enable_protoc_lib(module):
     module.shared_libs.append('libprotoc')
 
@@ -187,16 +171,14 @@
 # Android equivalents for third-party libraries that the upstream project
 # depends on.
 builtin_deps = {
-    '//buildtools:gmock': enable_gmock,
-    '//buildtools:gtest': enable_gtest,
-    '//buildtools:gtest_main': enable_gtest,
-    '//buildtools:libunwind': enable_libunwind,
-    '//buildtools:protobuf_full': enable_protobuf_full,
-    '//buildtools:protobuf_lite': enable_protobuf_lite,
-    '//buildtools:protoc_lib': enable_protoc_lib,
-    '//buildtools:libunwindstack': enable_libunwindstack,
-    '//buildtools:sqlite': enable_sqlite,
-    '//buildtools:zlib': enable_zlib,
+    '//gn:gtest_and_gmock': enable_gmock,
+    '//gn:libunwind': enable_libunwind,
+    '//gn:protobuf_full': enable_protobuf_full,
+    '//gn:protobuf_lite': enable_protobuf_lite,
+    '//gn:protoc_lib': enable_protoc_lib,
+    '//gn:libunwindstack': enable_libunwindstack,
+    '//gn:sqlite': enable_sqlite,
+    '//gn:zlib': enable_zlib,
 }
 
 # ----------------------------------------------------------------------------
@@ -419,7 +401,10 @@
         dep_name: GN target of the dependency.
     """
     # If the dependency refers to a library which we can replace with an Android
-    # equivalent, stop recursing and patch the dependency in.
+    # equivalent, stop recursing and patch the dependency in. Don't recurse into
+    # //buildtools, builtin_deps are intercepted at the //gn:xxx level.
+    if dep_name.startswith('//buildtools'):
+        return
     if gn_utils.label_without_toolchain(dep_name) in builtin_deps:
         builtin_deps[gn_utils.label_without_toolchain(dep_name)](module)
         return
@@ -645,7 +630,7 @@
     if target['type'] == 'executable':
         if 'host' in target['toolchain'] or target_name in target_host_only:
             module_type = 'cc_binary_host'
-        elif target.get('testonly') and target_name not in non_test_binaries:
+        elif target.get('testonly'):
             module_type = 'cc_test'
         else:
             module_type = 'cc_binary'
diff --git a/tools/gen_bazel b/tools/gen_bazel
index baabfc3..282bf84 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -385,10 +385,14 @@
       else:
         target.srcs.add(label)
 
-    if target.type == 'cc_library' and ('//include/perfetto/base/build_config.h'
-        in module_desc.get('sources', [])):
-      target.hdrs.add(Label(os.path.join(buildflags_dir,
-          'perfetto_build_flags.h')))
+    if '//include/perfetto/base/build_config.h' in module_desc.get(
+        'sources', []):
+      label = Label(os.path.join(buildflags_dir, 'perfetto_build_flags.h'))
+      if target.type == 'cc_library':
+          target.hdrs.add(label)
+      elif target.type == 'cc_binary':
+        target.srcs.add(label)
+
 
   def apply_module_dependency(self, target, dep_name):
     """
diff --git a/tools/gen_merged_protos b/tools/gen_merged_protos
index 319b833..ad632fb 100755
--- a/tools/gen_merged_protos
+++ b/tools/gen_merged_protos
@@ -53,6 +53,7 @@
   'protos/perfetto/trace/android/android_log.proto',
   'protos/perfetto/trace/android/graphics_frame_event.proto',
   'protos/perfetto/trace/android/packages_list.proto',
+  'protos/perfetto/trace/appended_data/appended_data.proto',
   'protos/perfetto/trace/chrome/chrome_benchmark_metadata.proto',
   "protos/perfetto/trace/chrome/chrome_metadata.proto",
   'protos/perfetto/trace/clock_snapshot.proto',
@@ -68,7 +69,6 @@
   'protos/perfetto/trace/ftrace/ftrace_event_bundle.proto',
   'protos/perfetto/trace/ftrace/ftrace_stats.proto',
   'protos/perfetto/trace/ftrace/generic.proto',
-  'protos/perfetto/trace/ftrace/gpu.proto',
   'protos/perfetto/trace/ftrace/kmem.proto',
   'protos/perfetto/trace/ftrace/lowmemorykiller.proto',
   'protos/perfetto/trace/ftrace/mm_event.proto',
diff --git a/tools/proto_to_cpp/proto_to_cpp.cc b/tools/proto_to_cpp/proto_to_cpp.cc
index 8bb103c..5e995f2 100644
--- a/tools/proto_to_cpp/proto_to_cpp.cc
+++ b/tools/proto_to_cpp/proto_to_cpp.cc
@@ -18,7 +18,6 @@
 #include <google/protobuf/dynamic_message.h>
 #include <google/protobuf/io/printer.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <google/protobuf/stubs/strutil.h>
 #include <google/protobuf/util/field_comparator.h>
 #include <google/protobuf/util/message_differencer.h>
 
@@ -28,10 +27,16 @@
 #include <iostream>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 
 using namespace google::protobuf;
 using namespace google::protobuf::compiler;
 using namespace google::protobuf::io;
+using perfetto::base::SplitString;
+using perfetto::base::StripChars;
+using perfetto::base::StripSuffix;
+using perfetto::base::ToUpper;
+
 static constexpr auto TYPE_MESSAGE = FieldDescriptor::TYPE_MESSAGE;
 
 namespace {
@@ -81,7 +86,7 @@
 };
 
 std::string GetProtoHeader(const FileDescriptor* proto_file) {
-  return StringReplace(proto_file->name(), ".proto", ".pb.h", false);
+  return StripSuffix(proto_file->name(), ".proto") + ".pb.h";
 }
 
 std::string GetFwdDeclType(const Descriptor* msg, bool with_namespace = false) {
@@ -92,7 +97,8 @@
     full_type.insert(0, par->name() + "_");
   }
   if (with_namespace) {
-    std::vector<std::string> namespaces = Split(msg->file()->package(), ".");
+    std::vector<std::string> namespaces =
+        SplitString(msg->file()->package(), ".");
     for (auto it = namespaces.rbegin(); it != namespaces.rend(); it++) {
       full_type.insert(0, *it + "::");
     }
@@ -153,18 +159,18 @@
 }
 
 std::string ProtoToCpp::GetHeaderPath(const FileDescriptor* proto_file) {
-  std::string basename = Split(proto_file->name(), "/").back();
-  return header_dir_ + "/" + StringReplace(basename, ".proto", ".h", false);
+  std::string basename = SplitString(proto_file->name(), "/").back();
+  return header_dir_ + "/" + StripSuffix(basename, ".proto") + ".h";
 }
 
 std::string ProtoToCpp::GetCppPath(const FileDescriptor* proto_file) {
-  std::string basename = Split(proto_file->name(), "/").back();
-  return cpp_dir_ + "/" + StringReplace(basename, ".proto", ".cc", false);
+  std::string basename = SplitString(proto_file->name(), "/").back();
+  return cpp_dir_ + "/" + StripSuffix(basename, ".proto") + ".cc";
 }
 
 std::string ProtoToCpp::GetIncludePath(const FileDescriptor* proto_file) {
-  std::string basename = Split(proto_file->name(), "/").back();
-  return include_path_ + "/" + StringReplace(basename, ".proto", ".h", false);
+  std::string basename = SplitString(proto_file->name(), "/").back();
+  return include_path_ + "/" + StripSuffix(basename, ".proto") + ".h";
 }
 
 std::string ProtoToCpp::GetCppType(const FieldDescriptor* field,
@@ -228,8 +234,8 @@
   Printer cpp_printer(&cpp_proto_ostr, '$');
 
   std::string include_guard = dst_header + "_";
-  UpperString(&include_guard);
-  StripString(&include_guard, ".-/\\", '_');
+  include_guard = ToUpper(include_guard);
+  include_guard = StripChars(include_guard, ".-/\\", '_');
   header_printer.Print(kHeader, "f", __FILE__, "p", src_proto);
   header_printer.Print("#ifndef $g$\n#define $g$\n\n", "g", include_guard);
   header_printer.Print("#include <stdint.h>\n");
@@ -278,7 +284,7 @@
 
   // Generate forward declarations in the header for proto types.
   header_printer.Print("// Forward declarations for protobuf types.\n");
-  std::vector<std::string> namespaces = Split(proto_file->package(), ".");
+  std::vector<std::string> namespaces = SplitString(proto_file->package(), ".");
   for (size_t i = 0; i < namespaces.size(); i++)
     header_printer.Print("namespace $n$ {\n", "n", namespaces[i]);
   for (int i = 0; i < proto_file->message_type_count(); i++)
diff --git a/tools/tmux b/tools/tmux
index 9f00b5f..7bc2734 100755
--- a/tools/tmux
+++ b/tools/tmux
@@ -39,25 +39,8 @@
   test "$value" == "android"
 }
 
-function is_gn_target_and_host_value_equal() {
-  local out=$1
-  local key=$2
-  local host
-  local target
-  host=$(get_gn_value "$out" "host_$key")
-  target=$(get_gn_value "$out" "target_$key")
-  [ -z "$target" ] && target="$host"
-  test "$target" == "$host"
-}
-
-function is_cross_compilation() {
-  local out=$1
-  if is_gn_target_and_host_value_equal "$out" cpu &&
-    is_gn_target_and_host_value_equal "$out" os; then
-    false
-  else
-    true
-  fi
+function is_ssh_target() {
+  [[ -n "$SSH_TARGET" ]];
 }
 
 function is_mac() {
@@ -85,9 +68,9 @@
     echo 0 > /sys/kernel/debug/tracing/tracing_on
     '
 
-    if is_cross_compilation "$OUT"; then
+    if is_ssh_target; then
       # shellcheck disable=SC2029
-      ssh "$TARGET" "sh -c '$script'"
+      ssh "$SSH_TARGET" "sh -c '$script'"
     else
       sh -c "$script"
     fi
@@ -106,9 +89,9 @@
     fi
     echo adb push $maybe_sync "$1" "$DIR"
     adb push $maybe_sync "$1" "$DIR"
-  elif is_cross_compilation "$OUT"; then
-    echo scp "$1" "$TARGET:$DIR"
-    scp "$1" "$TARGET:$DIR"
+  elif is_ssh_target; then
+    echo scp "$1" "$SSH_TARGET:$DIR"
+    scp "$1" "$SSH_TARGET:$DIR"
   else
     echo cp "$1" "$DIR"
     cp "$1" "$DIR"
@@ -119,9 +102,9 @@
   if is_android "$OUT"; then
     echo adb pull "$DIR/$1" "$2"
     adb pull "$DIR/$1" "$2"
-  elif is_cross_compilation "$OUT"; then
-    echo scp "$TARGET:$DIR/$1" "$2"
-    scp "$TARGET:$DIR/$1" "$2"
+  elif is_ssh_target; then
+    echo scp "$SSH_TARGET:$DIR/$1" "$2"
+    scp "$SSH_TARGET:$DIR/$1" "$2"
   else
     echo mv "$DIR/$1" "$2"
     mv "$DIR/$1" "$2"
@@ -131,15 +114,17 @@
 BACKGROUND=0
 SKIP_CONVERTERS=0
 TMUX_LAYOUT="even-vertical"
+CPU_MASK=""
 
-while getopts "bl:nt:c:C:" o; do
+while getopts "bl:nt:c:C:z:" o; do
   case "$o" in
     b) BACKGROUND=1 ;;
     l) TMUX_LAYOUT=${OPTARG} ;;
     n) SKIP_CONVERTERS=1 ;;
-    t) TARGET=${OPTARG} ;;
+    t) SSH_TARGET=${OPTARG} ;;
     c) CONFIG=${OPTARG} ;;
     C) OUT=${OPTARG} ;;
+    z) CPU_MASK=${OPTARG} ;;
     *)
       echo "Invalid option $o"
       exit
@@ -157,16 +142,17 @@
   echo ""
   echo "Options:"
   echo "  -b          run in the background"
-  echo "  -l          tmux pane layout"
+  echo "  -l LAYOUT   tmux pane layout"
   echo "  -n          skip post-trace convertors"
   echo "  -t TARGET   SSH device target"
   echo "  -c CONFIG   trace configuration file"
   echo "  -C OUTPUT   output directory"
+  echo "  -z MASK     constrain binaries to given cpu mask (taskset syntax)"
   echo ""
   echo "Environment variables:"
-  echo "  TARGET      SSH device target"
+  echo "  SSH_TARGET  SSH device target"
   echo "  CONFIG      trace configuration file"
-  echo "  OUTPUT      output directory"
+  echo "  OUT         output directory"
   exit 1
 fi
 
@@ -179,10 +165,10 @@
 fi
 
 # If we are cross-compiling we need to know the SSH target
-if is_cross_compilation "$OUT" && ! ssh -q "$TARGET" exit; then
-  echo "TARGET=$TARGET doesn't look like a valid SSH target."
+if is_ssh_target && ! ssh -q "$SSH_TARGET" exit; then
+  echo "SSH_TARGET=$SSH_TARGET doesn't look like a valid SSH target."
   echo "Please specify a SSH cross-compilation target by doing:"
-  echo "  export TARGET=<user>@<host> $0"
+  echo "  export SSH_TARGET=<user>@<host> $0"
   exit 1
 fi
 
@@ -192,8 +178,8 @@
 
 if is_android "$OUT"; then
   DIR=/data/local/tmp
-elif is_cross_compilation "$OUT"; then
-  DIR=$(ssh "$TARGET" mktemp -d $TMPDIR/perfetto.XXXXXX)
+elif is_ssh_target; then
+  DIR=$(ssh "$SSH_TARGET" mktemp -d $TMPDIR/perfetto.XXXXXX)
 elif is_mac; then
   DIR=$(mktemp -d $TMPDIR/perfetto.XXXXXX)
 else
@@ -245,6 +231,10 @@
 
 POSTFIX=""
 
+if [[ -n "$CPU_MASK" ]]; then
+  PREFIX="$PREFIX taskset $CPU_MASK"
+fi
+
 if [[ BACKGROUND -eq 1 ]]; then
   PREFIX="$PREFIX nohup"
   POSTFIX=" &> /dev/null &"
@@ -290,31 +280,31 @@
 sleep 2
 
 tmux select-pane -t 1
-if is_cross_compilation "$OUT"; then
-  tmux send-keys "ssh $TARGET" Enter
+if is_ssh_target; then
+  tmux send-keys "ssh $SSH_TARGET" Enter
 fi
 tmux_ensure_bash
 tmux send-keys "PS1='[traced]$ '" Enter
 tmux send-keys "cd $DIR" Enter
-tmux send-keys "$PREFIX ./traced 2>&1 | tee traced.log $POSTFIX" Enter
+tmux send-keys "$PREFIX ./traced $POSTFIX" Enter
 
 tmux select-pane -t 0
-if is_cross_compilation "$OUT"; then
-  tmux send-keys "ssh $TARGET" Enter
+if is_ssh_target; then
+  tmux send-keys "ssh $SSH_TARGET" Enter
 fi
 tmux_ensure_bash
 tmux send-keys "PS1='[traced_probes]$ '" Enter
 tmux send-keys "cd $DIR" Enter
-tmux send-keys "$PREFIX ./traced_probes 2>&1 | tee traced_probes.log $POSTFIX" Enter
+tmux send-keys "$PREFIX ./traced_probes $POSTFIX" Enter
 
 tmux select-pane -t 2
-if is_cross_compilation "$OUT"; then
-  tmux send-keys "ssh $TARGET" Enter
+if is_ssh_target; then
+  tmux send-keys "ssh $SSH_TARGET" Enter
 fi
 tmux_ensure_bash
 tmux send-keys "PS1='[consumer]$ '" Enter
 tmux send-keys "cd $DIR" Enter
-tmux send-keys "$PREFIX ./perfetto $CMD_OPTS -c $CONFIG_DEVICE_PATH -o trace 2>&1 | tee perfetto.log $POSTFIX"
+tmux send-keys "$PREFIX ./perfetto $CMD_OPTS -c $CONFIG_DEVICE_PATH -o trace $POSTFIX"
 
 # Select consumer pane.
 tmux select-pane -t 2
diff --git a/tools/trace_processor b/tools/trace_processor
index 00ac9b0..c8fb203 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -31,8 +31,8 @@
 import urllib
 
 TRACE_PROCESSOR_SHELL_SHAS = {
-  'linux': 'a610c3f4a65f543a3d1ef8f3c7063b071435332d',
-  'mac': 'fe8d751086265c4da258fa2364e07c069a1e0427',
+  'linux': '8e7ce3affdaa1d1f66a0bd58368782784bf6c9a6',
+  'mac':'78709d06d30260119f31963143073dde24f03da0',
 }
 TRACE_PROCESSOR_SHELL_PATH = tempfile.gettempdir()
 TRACE_PROCESSOR_SHELL_BASE_URL = (
diff --git a/tools/trace_to_text/local_symbolizer.cc b/tools/trace_to_text/local_symbolizer.cc
index 8b219a4..6ec1d96 100644
--- a/tools/trace_to_text/local_symbolizer.cc
+++ b/tools/trace_to_text/local_symbolizer.cc
@@ -18,6 +18,7 @@
 #include "tools/trace_to_text/local_symbolizer.h"
 
 #include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
 
 #include <elf.h>
@@ -52,7 +53,8 @@
     n = 0;
   } while (rd > 1);
   return lines;
-};
+}
+
 
 struct Elf32 {
   using Ehdr = Elf32_Ehdr;
@@ -165,6 +167,16 @@
   return *endptr == '\0' && parsed_line_no >= 0;
 }
 
+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",
+                            hex_build_id.c_str());
+    return {};
+  }
+
+  return hex_build_id.substr(0, 2) + "/" + hex_build_id.substr(2);
+}
+
 }  // namespace
 
 base::Optional<std::string> LocalBinaryFinder::FindBinary(
@@ -232,7 +244,9 @@
   std::string dirname;
 
   for (base::StringSplitter sp(abspath, '/'); sp.Next();) {
-    dirname += "/" + filename;
+    if (!dirname.empty())
+      dirname += "/";
+    dirname += filename;
     filename = sp.cur_token();
   }
 
@@ -243,12 +257,18 @@
   // * only filename of library file relative to root.
   // * only filename of library file relative to root, but with base.apk!
   //   removed from filename.
+  // * in the subdirectory .build-id: the first two hex digits of the build-id
+  //   as subdirectory, then the rest of the hex digits, with ".debug"appended.
+  //   See
+  //   https://fedoraproject.org/wiki/RolandMcGrath/BuildID#Find_files_by_build_ID
   //
-  // For example, "/system/lib/base.apk!foo.so" is looked for at
+  // For example, "/system/lib/base.apk!foo.so" with build id abcd1234,
+  // is looked for at
   // * $ROOT/system/lib/base.apk!foo.so
   // * $ROOT/system/lib/foo.so
   // * $ROOT/base.apk!foo.so
   // * $ROOT/foo.so
+  // * $ROOT/.build-id/ab/cd1234.debug
 
   std::string symbol_file = root_str + "/" + dirname + "/" + filename;
   if (access(symbol_file.c_str(), F_OK) == 0 &&
@@ -275,6 +295,16 @@
       return {symbol_file};
   }
 
+  std::string hex_build_id = base::ToHex(build_id.c_str(), build_id.size());
+  std::string split_hex_build_id = SplitBuildID(hex_build_id);
+  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};
+  }
+
   return base::nullopt;
 }
 
@@ -315,7 +345,7 @@
     uint64_t address) {
   std::vector<SymbolizedFrame> result;
 
-  if (PERFETTO_EINTR(dprintf(subprocess_.write_fd(), "%s 0x%lx\n",
+  if (PERFETTO_EINTR(dprintf(subprocess_.write_fd(), "%s 0x%" PRIx64 "\n",
                              binary.c_str(), address)) < 0) {
     PERFETTO_ELOG("Failed to write to llvm-symbolizer.");
     return result;
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index 7a41663..f3f1fe1 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -31,6 +31,7 @@
 #include "tools/trace_to_text/utils.h"
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
 
 #include "protos/perfetto/trace/profiling/profile_common.pb.h"
 #include "protos/perfetto/trace/profiling/profile_packet.pb.h"
@@ -67,15 +68,6 @@
 using GFunction = ::perfetto::third_party::perftools::profiles::Function;
 using GSample = ::perfetto::third_party::perftools::profiles::Sample;
 
-std::string ToHex(const std::string& build_id) {
-  std::string hex_build_id(2 * build_id.size() + 1, ' ');
-  for (size_t i = 0; i < build_id.size(); ++i)
-    snprintf(&(hex_build_id[2 * i]), 3, "%02hhx", build_id[i]);
-  // Remove the trailing nullbyte.
-  hex_build_id.resize(2 * build_id.size());
-  return hex_build_id;
-}
-
 enum Strings : int64_t {
   kEmpty = 0,
   kObjects,
@@ -163,7 +155,7 @@
     auto str_it = string_lookup_.find(mapping.build_id());
     if (str_it != string_lookup_.end()) {
       const std::string& build_id = str_it->second;
-      gmapping->set_build_id(InternInGProfile(ToHex(build_id)));
+      gmapping->set_build_id(InternInGProfile(base::ToHex(build_id)));
     }
     return true;
   }
@@ -303,17 +295,17 @@
 };
 
 bool DumpProfilePacket(const std::vector<ProfilePacket>& packet_fragments,
-                       const std::vector<InternedData>& interned_data,
+                       const SequencedBundle& bundle,
                        std::vector<SerializedProfile>* output,
                        Symbolizer* symbolizer) {
   TraceSymbolTable symbol_table(symbolizer);
-  if (!symbol_table.Visit(packet_fragments, interned_data))
+  if (!symbol_table.Visit(packet_fragments, bundle))
     return false;
   if (!symbol_table.Finalize())
     return false;
 
   GProfileWriter writer(&symbol_table);
-  if (!writer.Visit(packet_fragments, interned_data))
+  if (!writer.Visit(packet_fragments, bundle))
     return false;
 
   if (!writer.Finalize())
@@ -344,9 +336,8 @@
   return VisitCompletePacket(
       input, [output, symbolizer](
                  uint32_t, const std::vector<ProfilePacket>& packet_fragments,
-                 const std::vector<InternedData>& interned_data) {
-        return DumpProfilePacket(packet_fragments, interned_data, output,
-                                 symbolizer);
+                 const SequencedBundle& bundle) {
+        return DumpProfilePacket(packet_fragments, bundle, output, symbolizer);
       });
 }
 
diff --git a/tools/trace_to_text/profile_visitor.cc b/tools/trace_to_text/profile_visitor.cc
index 065222d..a2b729b 100644
--- a/tools/trace_to_text/profile_visitor.cc
+++ b/tools/trace_to_text/profile_visitor.cc
@@ -16,6 +16,7 @@
 
 #include "tools/trace_to_text/profile_visitor.h"
 
+#include <unordered_map>
 #include "protos/perfetto/trace/trace.pb.h"
 #include "protos/perfetto/trace/trace_packet.pb.h"
 
@@ -32,16 +33,33 @@
 using ::perfetto::protos::Mapping;
 using ::perfetto::protos::ProfiledFrameSymbols;
 using ::perfetto::protos::ProfilePacket;
+
+struct ProfilePackets {
+  uint32_t seq_id;
+  std::vector<protos::ProfilePacket> packets;
+};
+
+bool IsPacketIndexContiguous(
+    const std::vector<perfetto::protos::ProfilePacket>& packets) {
+  for (size_t i = 1; i < packets.size(); ++i) {
+    // Ensure we are not missing a chunk.
+    if (packets[i - 1].index() + 1 != packets[i].index()) {
+      return false;
+    }
+  }
+  return true;
+}
 }  // namespace
 
-bool ProfileVisitor::Visit(const std::vector<ProfilePacket>& packet_fragments,
-                           const std::vector<InternedData>& interned_data) {
+bool ProfileVisitor::Visit(
+    const std::vector<protos::ProfilePacket>& packet_fragments,
+    const SequencedBundle& bundle) {
   for (const ProfilePacket& packet : packet_fragments) {
     for (const InternedString& interned_string : packet.strings())
       if (!AddInternedString(interned_string))
         return false;
   }
-  for (const InternedData& data : interned_data) {
+  for (const InternedData& data : bundle.interned_data) {
     for (const InternedString& interned_string : data.build_ids())
       if (!AddInternedString(interned_string))
         return false;
@@ -54,17 +72,21 @@
     for (const InternedString& interned_string : data.source_paths())
       if (!AddInternedString(interned_string))
         return false;
+    // TODO (140860736): This should be outside the interned section.
     for (const ProfiledFrameSymbols& pfs : data.profiled_frame_symbols())
       if (!AddProfiledFrameSymbols(pfs))
         return false;
   }
+  for (const ProfiledFrameSymbols& pfs : bundle.symbols)
+    if (!AddProfiledFrameSymbols(pfs))
+      return false;
 
   for (const ProfilePacket& packet : packet_fragments) {
     for (const Callstack& callstack : packet.callstacks())
       if (!AddCallstack(callstack))
         return false;
   }
-  for (const InternedData& data : interned_data) {
+  for (const InternedData& data : bundle.interned_data) {
     for (const Callstack& callstack : data.callstacks())
       if (!AddCallstack(callstack))
         return false;
@@ -75,7 +97,7 @@
       if (!AddMapping(mapping))
         return false;
   }
-  for (const InternedData& data : interned_data) {
+  for (const InternedData& data : bundle.interned_data) {
     for (const Mapping& callstack : data.mappings()) {
       if (!AddMapping(callstack))
         return false;
@@ -88,7 +110,7 @@
         return false;
     }
   }
-  for (const InternedData& data : interned_data) {
+  for (const InternedData& data : bundle.interned_data) {
     for (const Frame& frame : data.frames()) {
       if (!AddFrame(frame))
         return false;
@@ -99,5 +121,54 @@
 
 ProfileVisitor::~ProfileVisitor() = default;
 
+bool VisitCompletePacket(
+    std::istream* input,
+    const std::function<bool(uint32_t,
+                             const std::vector<protos::ProfilePacket>&,
+                             const SequencedBundle&)>& fn) {
+  // Rolling profile packets per seq id. Cleared on finalization.
+  std::unordered_map<uint32_t, std::vector<protos::ProfilePacket>>
+      rolling_profile_packets_by_seq;
+  std::vector<ProfilePackets> complete_profile_packets;
+  // Append-only interned data and symbols by seq id
+  std::unordered_map<uint32_t, SequencedBundle> bundle_by_seq;
+  ForEachPacketInTrace(input, [&rolling_profile_packets_by_seq,
+                               &complete_profile_packets, &bundle_by_seq](
+                                  const protos::TracePacket& packet) {
+    uint32_t seq_id = packet.trusted_packet_sequence_id();
+    if (packet.has_interned_data()) {
+      bundle_by_seq[seq_id].interned_data.emplace_back(packet.interned_data());
+    }
+    if (packet.has_appended_data()) {
+      std::copy(packet.appended_data().profiled_frame_symbols().cbegin(),
+                packet.appended_data().profiled_frame_symbols().cend(),
+                std::back_inserter(bundle_by_seq[seq_id].symbols));
+    }
+
+    if (packet.has_profile_packet()) {
+      std::vector<protos::ProfilePacket>& rolling_profile_packets =
+          rolling_profile_packets_by_seq[seq_id];
+      rolling_profile_packets.emplace_back(packet.profile_packet());
+
+      if (!packet.profile_packet().continued()) {
+        if (IsPacketIndexContiguous(rolling_profile_packets)) {
+          complete_profile_packets.push_back({seq_id, rolling_profile_packets});
+          rolling_profile_packets_by_seq.erase(seq_id);
+        }
+      }
+    }
+  });
+
+  bool success = true;
+  for (const auto& packets : complete_profile_packets) {
+    success &=
+        fn(packets.seq_id, packets.packets, bundle_by_seq[packets.seq_id]);
+  }
+  if (!rolling_profile_packets_by_seq.empty()) {
+    PERFETTO_ELOG("WARNING: Truncated heap dump.");
+    return false;
+  }
+  return success;
+}
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/profile_visitor.h b/tools/trace_to_text/profile_visitor.h
index 54c3ec2..4723f14 100644
--- a/tools/trace_to_text/profile_visitor.h
+++ b/tools/trace_to_text/profile_visitor.h
@@ -31,10 +31,14 @@
 namespace perfetto {
 namespace trace_to_text {
 
+struct SequencedBundle {
+  std::vector<protos::InternedData> interned_data;
+  std::vector<protos::ProfiledFrameSymbols> symbols;
+};
+
 class ProfileVisitor {
  public:
-  bool Visit(const std::vector<protos::ProfilePacket>&,
-             const std::vector<protos::InternedData>&);
+  bool Visit(const std::vector<protos::ProfilePacket>&, const SequencedBundle&);
   virtual bool AddInternedString(
       const protos::InternedString& interned_string) = 0;
   virtual bool AddCallstack(const protos::Callstack& callstack) = 0;
@@ -45,54 +49,11 @@
   virtual ~ProfileVisitor();
 };
 
-template <typename F>
-bool VisitCompletePacket(std::istream* input, F fn) {
-  std::map<uint32_t, std::vector<protos::ProfilePacket>>
-      rolling_profile_packets_by_seq;
-  std::map<uint32_t, std::vector<protos::InternedData>>
-      rolling_interned_data_by_seq;
-  bool success = true;
-  ForEachPacketInTrace(input, [&rolling_profile_packets_by_seq,
-                               &rolling_interned_data_by_seq, &success,
-                               &fn](const protos::TracePacket& packet) {
-    uint32_t seq_id = packet.trusted_packet_sequence_id();
-    if (packet.has_interned_data())
-      rolling_interned_data_by_seq[seq_id].emplace_back(packet.interned_data());
-
-    if (!packet.has_profile_packet())
-      return;
-
-    rolling_profile_packets_by_seq[seq_id].emplace_back(
-        packet.profile_packet());
-
-    const std::vector<protos::InternedData>& rolling_interned_data =
-        rolling_interned_data_by_seq[seq_id];
-    const std::vector<protos::ProfilePacket>& rolling_profile_packets =
-        rolling_profile_packets_by_seq[seq_id];
-
-    if (!packet.profile_packet().continued()) {
-      for (size_t i = 1; i < rolling_profile_packets.size(); ++i) {
-        // Ensure we are not missing a chunk.
-        if (rolling_profile_packets[i - 1].index() + 1 !=
-            rolling_profile_packets[i].index()) {
-          success = false;
-          return;
-        }
-      }
-      if (!fn(seq_id, rolling_profile_packets, rolling_interned_data))
-        success = false;
-
-      // We do not clear rolling_interned_data, as it is globally scoped.
-      rolling_profile_packets_by_seq.erase(seq_id);
-    }
-  });
-
-  if (!rolling_profile_packets_by_seq.empty()) {
-    PERFETTO_ELOG("WARNING: Truncated heap dump.");
-    return false;
-  }
-  return success;
-}
+bool VisitCompletePacket(
+    std::istream* input,
+    const std::function<bool(uint32_t,
+                             const std::vector<protos::ProfilePacket>&,
+                             const SequencedBundle&)>& fn);
 
 }  // namespace trace_to_text
 }  // namespace perfetto
diff --git a/tools/trace_to_text/symbolize_profile.cc b/tools/trace_to_text/symbolize_profile.cc
index 0902450..c7c64c2 100644
--- a/tools/trace_to_text/symbolize_profile.cc
+++ b/tools/trace_to_text/symbolize_profile.cc
@@ -54,9 +54,9 @@
       input, [&output, &symbolizer](
                  uint32_t seq_id,
                  const std::vector<protos::ProfilePacket>& packet_fragments,
-                 const std::vector<protos::InternedData>& interned_data) {
+                 const SequencedBundle& bundle) {
         TraceSymbolTable symbol_table(symbolizer.get());
-        if (!symbol_table.Visit(packet_fragments, interned_data))
+        if (!symbol_table.Visit(packet_fragments, bundle))
           return false;
         symbol_table.Finalize();
         symbol_table.WriteResult(output, seq_id);
diff --git a/tools/trace_to_text/trace_symbol_table.cc b/tools/trace_to_text/trace_symbol_table.cc
index c171993..b345160 100644
--- a/tools/trace_to_text/trace_symbol_table.cc
+++ b/tools/trace_to_text/trace_symbol_table.cc
@@ -96,12 +96,13 @@
 
 void TraceSymbolTable::WriteResult(std::ostream* output,
                                    uint32_t seq_id) const {
-  protos::TracePacket intern_packet;
-  intern_packet.set_trusted_packet_sequence_id(seq_id);
-  std::vector<protos::TracePacket> frame_symbol_packets;
+  protos::TracePacket appended_packet;
+  appended_packet.set_trusted_packet_sequence_id(seq_id);
+
   auto max_string_intern_id = max_string_intern_id_;
   std::map<std::string, uint64_t> new_interned_strings;
-  protos::InternedData* interned_data = intern_packet.mutable_interned_data();
+  protos::InternedData* interned_data = appended_packet.mutable_interned_data();
+  protos::AppendedData* appended_data = appended_packet.mutable_appended_data();
   for (const auto& p : symbols_for_frame_) {
     uint64_t frame_iid = p.first;
     const std::vector<SymbolizedFrame>& frames = p.second;
@@ -126,25 +127,16 @@
       }
     }
 
-    protos::TracePacket symbol_packet;
-    symbol_packet.set_trusted_packet_sequence_id(seq_id);
     protos::ProfiledFrameSymbols* sym =
-        symbol_packet.mutable_profiled_frame_symbols();
+        appended_data->add_profiled_frame_symbols();
     sym->set_frame_iid(frame_iid);
     for (const SymbolizedFrame& frame : frames) {
       sym->add_function_name_id(new_interned_strings[frame.function_name]);
       sym->add_line_number(frame.line);
       sym->add_file_name_id(new_interned_strings[frame.file_name]);
     }
-    // TODO(138725313): Cleanup - this should be outside the interned section.
-    *interned_data->add_profiled_frame_symbols() = *sym;
-
-    frame_symbol_packets.push_back(std::move(symbol_packet));
   }
-  WriteTracePacket(intern_packet.SerializeAsString(), output);
-  for (const auto& frame_symbol_packet : frame_symbol_packets) {
-    WriteTracePacket(frame_symbol_packet.SerializeAsString(), output);
-  }
+  WriteTracePacket(appended_packet.SerializeAsString(), output);
 }
 
 bool TraceSymbolTable::AddProfiledFrameSymbols(
diff --git a/tools/traceconv b/tools/traceconv
index 71675ed..fc5f213 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -33,8 +33,8 @@
 # Keep this in sync with the SHAs in catapult file
 # systrace/systrace/tracing_agents/atrace_from_file_agent.py.
 TRACE_TO_TEXT_SHAS = {
-  'linux': 'd67f9f3e3beb93664578c8b0300ab98e828ecb29',
-  'mac': '62b27f343640d0778f1324fa90940381561bef99',
+  'linux': 'bbd4849ccc18e216c827a9ae14361910f29307c4',
+  'mac':'12467778e99769b8c37df037043cbb99475b3a0a',
 }
 TRACE_TO_TEXT_PATH = tempfile.gettempdir()
 TRACE_TO_TEXT_BASE_URL = (
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index e142346..6182663 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -326,6 +326,10 @@
           opacity: 0;
         }
 
+        .track-button.show {
+          opacity: 1;
+        }
+
         &:hover .track-button{
           opacity: 1;
         }
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index c5326b9..8c7a840 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -803,6 +803,39 @@
         box-shadow: 0 0 6px #ccc;
       }
     }
+    // Stop/cancel buttons
+    .buttons {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      width: auto;
+      height: 70px;
+      >* {
+        @include transition(0.2s);
+        cursor: pointer;
+        border-radius: 10px;
+        text-align: center;
+        margin: 3px;
+        background-color: #eee;
+        font-family: 'Raleway', sans-serif;
+        flex-grow: 1;
+        font-size: 17px;
+        @media (max-width: 1280px) {
+          font-size: 1.6vw;
+        }
+        padding: 7px;
+
+        &:hover {
+          background-color: hsl(88, 50%, 84%);
+          box-shadow: 0 0 4px 0px #999;
+        }
+
+        &.selected {
+          background-color: hsl(88, 50%, 67%);
+          box-shadow: 0 0 4px 0px #999;
+        }
+      }
+    }
 
     progress {
       -webkit-appearance: none;
diff --git a/ui/src/base/string_utils.ts b/ui/src/base/string_utils.ts
new file mode 100644
index 0000000..0ad7dd3
--- /dev/null
+++ b/ui/src/base/string_utils.ts
@@ -0,0 +1,30 @@
+// 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.
+
+export function uint8ArrayToBase64(buffer: Uint8Array): string {
+  return btoa(uint8ArrayToString(buffer));
+}
+
+export function uint8ArrayToString(buffer: Uint8Array): string {
+  return String.fromCharCode.apply(null, Array.from(buffer));
+}
+
+export function stringToUint8Array(str: string): Uint8Array {
+  const bufView = new Uint8Array(new ArrayBuffer(str.length));
+  const strLen = str.length;
+  for (let i = 0; i < strLen; i++) {
+    bufView[i] = str.charCodeAt(i);
+  }
+  return bufView;
+}
\ No newline at end of file
diff --git a/ui/src/base/string_utils_jsdomtest.ts b/ui/src/base/string_utils_jsdomtest.ts
new file mode 100644
index 0000000..e8bae93
--- /dev/null
+++ b/ui/src/base/string_utils_jsdomtest.ts
@@ -0,0 +1,40 @@
+// 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/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 512e633..d46f6b7 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -22,13 +22,15 @@
   GetTraceStatsResponse,
   ReadBuffersResponse
 } from '../controller/consumer_port_types';
+import {extractTraceConfig} from '../controller/record_controller';
+import {RpcConsumerPort} from '../controller/record_controller_interfaces';
 import {perfetto} from '../gen/protos';
 
 import {DevToolsSocket} from './devtools_socket';
 
 const CHUNK_SIZE: number = 1024 * 1024 * 64;
 
-export class ChromeTracingController {
+export class ChromeTracingController extends RpcConsumerPort {
   private streamHandle: string|undefined = undefined;
   private uiPort: chrome.runtime.Port;
   private api: ProtocolProxyApi.ProtocolApi;
@@ -36,6 +38,16 @@
   private lastBufferUsageEvent: Protocol.Tracing.BufferUsageEvent|undefined;
 
   constructor(port: chrome.runtime.Port) {
+    super({
+      onConsumerPortResponse: (message: ConsumerPortResponse) =>
+          this.uiPort.postMessage(message),
+
+      onError: (error: string) =>
+          this.uiPort.postMessage({type: 'ChromeExtensionError', error}),
+
+      onStatus: (status) =>
+          this.uiPort.postMessage({type: 'ChromeExtensionStatus', status})
+    });
     this.uiPort = port;
     this.devtoolsSocket = new DevToolsSocket();
     this.devtoolsSocket.on('close', () => this.resetState());
@@ -45,18 +57,10 @@
     this.api.Tracing.on('bufferUsage', this.onBufferUsage.bind(this));
   }
 
-  sendMessage(message: ConsumerPortResponse) {
-    this.uiPort.postMessage(message);
-  }
-
-  sendErrorMessage(error: string) {
-    this.uiPort.postMessage({type: 'ErrorResponse', result: {error}});
-  }
-
-  onMessage(request: {method: string, traceConfig: Uint8Array}) {
-    switch (request.method) {
+  handleCommand(methodName: string, requestData: Uint8Array) {
+    switch (methodName) {
       case 'EnableTracing':
-        this.enableTracing(request);
+        this.enableTracing(requestData);
         break;
       case 'FreeBuffers':
         this.freeBuffers();
@@ -74,19 +78,25 @@
         this.getCategories();
         break;
       default:
-        this.sendErrorMessage('Action not recognised');
-        console.log('Received not recognized message: ', request.method);
+        this.sendErrorMessage('Action not recognized');
+        console.log('Received not recognized message: ', methodName);
         break;
     }
   }
 
-  enableTracing(request: {method: string, traceConfig: Uint8Array}) {
+  enableTracing(enableTracingRequest: Uint8Array) {
     this.resetState();
-    const traceConfig = TraceConfig.decode(new Uint8Array(request.traceConfig));
+    const traceConfigProto = extractTraceConfig(enableTracingRequest);
+    if (!traceConfigProto) {
+      this.sendErrorMessage('Invalid trace config');
+      return;
+    }
+    const traceConfig = TraceConfig.decode(traceConfigProto);
     const chromeConfig = this.extractChromeConfig(traceConfig);
     this.handleStartTracing(chromeConfig);
   }
 
+  // TODO(nicomazz): write unit test for this
   extractChromeConfig(perfettoConfig: TraceConfig):
       Protocol.Tracing.TraceConfig {
     for (const ds of perfettoConfig.dataSources) {
@@ -106,7 +116,6 @@
   }
 
   async readBuffers(offset = 0) {
-    // TODO(nicomazz): Add error handling also in the frontend.
     if (!this.devtoolsSocket.isAttached() || this.streamHandle === undefined) {
       this.sendErrorMessage('No tracing session to read from');
       return;
diff --git a/ui/src/chrome_extension/index.ts b/ui/src/chrome_extension/index.ts
index 1bc5e36..5bce4ec 100644
--- a/ui/src/chrome_extension/index.ts
+++ b/ui/src/chrome_extension/index.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {stringToUint8Array} from '../base/string_utils';
 import {ChromeTracingController} from './chrome_tracing_controller';
 
 let chromeTraceController: ChromeTracingController|undefined = undefined;
@@ -24,14 +25,19 @@
 });
 
 function onUIMessage(
-    message: {method: string, traceConfig: Uint8Array},
-    port: chrome.runtime.Port) {
+    message: {method: string, requestData: string}, port: chrome.runtime.Port) {
   if (message.method === 'ExtensionVersion') {
     port.postMessage({version: chrome.runtime.getManifest().version});
     return;
   }
-  // In the future, more targets will be supported.
-  if (chromeTraceController) chromeTraceController.onMessage(message);
+  console.assert(chromeTraceController !== undefined);
+  if (!chromeTraceController) return;
+  // ChromeExtensionConsumerPort sends the request data as string because
+  // chrome.runtime.port doesn't support ArrayBuffers.
+  const requestDataArray: Uint8Array = message.requestData ?
+      stringToUint8Array(message.requestData) :
+      new Uint8Array();
+  chromeTraceController.handleCommand(message.method, requestDataArray);
 }
 
 function enableOnlyOnPerfettoHost() {
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 0850d3b..bd78842 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -153,6 +153,11 @@
     state.visibleTracks = args.tracks;
   },
 
+  updateTrackConfig(state: StateDraft, args: {id: string, config: {}}) {
+    if (state.tracks[args.id] === undefined) return;
+    state.tracks[args.id].config = args.config;
+  },
+
   executeQuery(
       state: StateDraft,
       args: {queryId: string; engineId: string; query: string}): void {
@@ -359,6 +364,23 @@
     };
   },
 
+  selectCounter(
+      state: StateDraft, args: {leftTs: number, rightTs: number, id: number}):
+      void {
+        state.currentSelection = {
+          kind: 'COUNTER',
+          leftTs: args.leftTs,
+          rightTs: args.rightTs,
+          id: args.id
+        };
+      },
+
+  selectHeapDump(
+      state: StateDraft, args: {id: number, upid: number, ts: number}): void {
+    state.currentSelection =
+        {kind: 'HEAP_DUMP', id: args.id, upid: args.upid, ts: args.ts};
+  },
+
   selectChromeSlice(state: StateDraft, args: {slice_id: number}): void {
     state.currentSelection = {kind: 'CHROME_SLICE', id: args.slice_id};
   },
@@ -398,12 +420,18 @@
   startRecording(state: StateDraft): void {
     state.recordingInProgress = true;
     state.lastRecordingError = undefined;
+    state.recordingCancelled = false;
   },
 
   stopRecording(state: StateDraft): void {
     state.recordingInProgress = false;
   },
 
+  cancelRecording(state: StateDraft): void {
+    state.recordingInProgress = false;
+    state.recordingCancelled = true;
+  },
+
   setExtensionAvailable(state: StateDraft, args: {available: boolean}): void {
     state.extensionInstalled = args.available;
   },
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 7485ad5..6288dd1 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -23,7 +23,8 @@
   [P in keyof T]: T[P];
 }&{lastUpdate: number};
 
-export type OmniboxState = Timestamped<{omnibox: string;}>;
+export type OmniboxState =
+    Timestamped<{omnibox: string; mode: 'SEARCH' | 'COMMAND'}>;
 
 export type VisibleState =
     Timestamped<{startSec: number; endSec: number; resolution: number;}>;
@@ -101,6 +102,20 @@
   id: number;
 }
 
+export interface CounterSelection {
+  kind: 'COUNTER';
+  leftTs: number;
+  rightTs: number;
+  id: number;
+}
+
+export interface HeapDumpSelection {
+  kind: 'HEAP_DUMP';
+  id: number;
+  upid: number;
+  ts: number;
+}
+
 export interface ChromeSliceSelection {
   kind: 'CHROME_SLICE';
   id: number;
@@ -121,8 +136,9 @@
   cpu: number;
 }
 
-type Selection = NoteSelection|SliceSelection|ChromeSliceSelection|
-    TimeSpanSelection|ThreadStateSelection;
+type Selection =
+    NoteSelection|SliceSelection|CounterSelection|HeapDumpSelection|
+    ChromeSliceSelection|TimeSpanSelection|ThreadStateSelection;
 
 export interface LogsPagination {
   offset: number;
@@ -184,6 +200,7 @@
    * Trace recording
    */
   recordingInProgress: boolean;
+  recordingCancelled: boolean;
   extensionInstalled: boolean;
   androidDeviceConnected?: AdbRecordingTarget;
   availableDevices: AdbRecordingTarget[];
@@ -237,7 +254,6 @@
   screenRecord: boolean;
 
   gpuFreq: boolean;
-  gpuSched: boolean;
 
   ftrace: boolean;
   atrace: boolean;
@@ -287,7 +303,6 @@
     screenRecord: false,
 
     gpuFreq: false,
-    gpuSched: false,
 
     ftrace: false,
     atrace: false,
@@ -347,6 +362,7 @@
       omniboxState: {
         lastUpdate: 0,
         omnibox: '',
+        mode: 'SEARCH',
       },
 
       visibleState: {
@@ -371,6 +387,7 @@
     scrubbingEnabled: false,
     flagPauseEnabled: false,
     recordingInProgress: false,
+    recordingCancelled: false,
     extensionInstalled: false,
     androidDeviceConnected: undefined,
     availableDevices: [],
diff --git a/ui/src/controller/adb.ts b/ui/src/controller/adb.ts
index 15c9139..6ba9515 100644
--- a/ui/src/controller/adb.ts
+++ b/ui/src/controller/adb.ts
@@ -24,6 +24,8 @@
   RSAPUBLICKEY = 3,
 }
 
+const DEVICE_NOT_SET_ERROR = 'Device not set.';
+
 // This class is a basic TypeScript implementation of adb that only supports
 // shell commands. It is used to send the start tracing command to the connected
 // android device, and to automatically pull the trace after the end of the
@@ -100,6 +102,15 @@
     return new Promise<void>((resolve, _) => this.onConnected = resolve);
   }
 
+  async disconnect(): Promise<void> {
+    if (!this.dev) {
+      console.error('adb disconnect() called with no device connected');
+      return;
+    }
+    this.dev.close();
+    this.dev = undefined;
+  }
+
   async startAuthentication() {
     // USB connected, now let's authenticate.
     const VERSION =
@@ -109,7 +120,7 @@
   }
 
   findInterfaceAndEndpoint() {
-    if (!this.dev) throw Error('Device not set');
+    if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR);
     for (const config of this.dev.configurations) {
       for (const interface_ of config.interfaces) {
         for (const alt of interface_.alternates) {
@@ -140,18 +151,18 @@
   }
 
   receiveDeviceMessages() {
-    // TODO(nicomazz): find the best (and correct) way to stop this.
-    try {
-      this.recv().then(msg => {
-        this.onMessage(msg);
-        this.receiveDeviceMessages();
-      });
-    } catch (e) {
-      // Then the usb connection is not available anymore, the recv will throw
-      // an exception here.
-      // TODO(nicomazz): Propagate this unconnected state to the UI.
-      console.log('exception on recv: ', e);
-    }
+    this.recv()
+        .then(msg => {
+          this.onMessage(msg);
+          this.receiveDeviceMessages();
+        })
+        .catch(e => {
+          // Ignore error with "DEVICE_NOT_SET_ERROR" message since it is always
+          // thrown after the device disconnects.
+          if (e.message !== DEVICE_NOT_SET_ERROR) {
+            console.error(`Exception in recv: ${e.name}. error: ${e.message}`);
+          }
+        });
   }
 
   async onMessage(msg: AdbMsg) {
@@ -244,6 +255,16 @@
     });
   }
 
+  async shellOutputAsString(cmd: string): Promise<string> {
+    const shell = await this.shell(cmd);
+
+    return new Promise<string>((resolve, _) => {
+      const output: string[] = [];
+      shell.onData = (str, _) => output.push(str);
+      shell.onClose = () => resolve(output.join());
+    });
+  }
+
   async send(
       cmd: CmdType, arg0: number, arg1: number, data?: Uint8Array|string) {
     await this.sendMsg(AdbMsgImpl.create(
@@ -265,14 +286,10 @@
     console.assert(res.status === 'ok');
     const msg = AdbMsgImpl.decodeHeader(res.data!);
 
-    let bytesLeft = msg.dataLen;
-    let bytesReceived = 0;
-    while (bytesLeft > 0) {
-      const resp = await this.recvRaw(bytesLeft);
-      for (let i = 0; i < resp.data!.byteLength; i++) {
-        msg.data[bytesReceived++] = resp.data!.getUint8(i);
-      }
-      bytesLeft -= resp.data!.byteLength;
+    if (msg.dataLen > 0) {
+      const resp = await this.recvRaw(msg.dataLen);
+      msg.data = new Uint8Array(
+          resp.data!.buffer, resp.data!.byteOffset, resp.data!.byteLength);
     }
     if (this.useChecksum) {
       console.assert(AdbOverWebUsb.checksum(msg.data) === msg.dataChecksum);
@@ -304,12 +321,12 @@
 
   sendRaw(buf: Uint8Array): Promise<USBOutTransferResult> {
     console.assert(buf.length <= this.maxPayload);
-    if (!this.dev) throw Error('Device not set');
+    if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR);
     return this.dev.transferOut(this.usbWriteEpEndpoint, buf.buffer);
   }
 
   recvRaw(dataLen: number): Promise<USBInTransferResult> {
-    if (!this.dev) throw Error('Device not set');
+    if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR);
     return this.dev.transferIn(this.usbReadEndpoint, dataLen);
   }
 }
@@ -363,7 +380,6 @@
     }
 
     this.adb.send('CLSE', this.localStreamId, this.remoteStreamId);
-    this.doClose();
   }
 
   async write(msg: string|Uint8Array) {
diff --git a/ui/src/controller/adb_interfaces.ts b/ui/src/controller/adb_interfaces.ts
index 91dd653..2f22a2f 100644
--- a/ui/src/controller/adb_interfaces.ts
+++ b/ui/src/controller/adb_interfaces.ts
@@ -15,12 +15,16 @@
 
 export interface Adb {
   connect(device: USBDevice): Promise<void>;
+  disconnect(): Promise<void>;
   shell(cmd: string): Promise<AdbStream>;
+  shellOutputAsString(cmd: string): Promise<string>;
 }
 
 export interface AdbStream {
   onMessage(message: AdbMsg): void;
   onData: (str: string, raw: Uint8Array) => void;
+  close(): void;
+
   onConnect: VoidCallback;
   onClose: VoidCallback;
 }
@@ -29,9 +33,18 @@
   connect(_: USBDevice): Promise<void> {
     return Promise.resolve();
   }
+
+  disconnect(): Promise<void> {
+    return Promise.resolve();
+  }
+
   shell(_: string): Promise<AdbStream> {
     return Promise.resolve(new MockAdbStream());
   }
+
+  shellOutputAsString(_: string): Promise<string> {
+    return Promise.resolve('');
+  }
 }
 
 export class MockAdbStream implements AdbStream {
@@ -39,6 +52,7 @@
   onConnect = () => {};
   onClose = () => {};
   onMessage = (_: AdbMsg) => {};
+  close() {}
 }
 
 export declare type CmdType =
diff --git a/ui/src/controller/adb_record_controller.ts b/ui/src/controller/adb_record_controller.ts
index f3d17cb..990053d 100644
--- a/ui/src/controller/adb_record_controller.ts
+++ b/ui/src/controller/adb_record_controller.ts
@@ -12,10 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {uint8ArrayToBase64} from '../base/string_utils';
+
 import {Adb, AdbStream} from './adb_interfaces';
-import {ConsumerPortResponse, ReadBuffersResponse} from './consumer_port_types';
+import {ReadBuffersResponse} from './consumer_port_types';
 import {globals} from './globals';
-import {RecordControllerMessage, uint8ArrayToBase64} from './record_controller';
+import {
+  extractDurationFromTraceConfig,
+  extractTraceConfig
+} from './record_controller';
+import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
 
 enum AdbState {
   READY,
@@ -24,39 +30,20 @@
 }
 const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace';
 
-export class AdbRecordController {
+export class AdbConsumerPort extends RpcConsumerPort {
   // public for testing
   traceDestFile = DEFAULT_DESTINATION_FILE;
   private state = AdbState.READY;
   private adb: Adb;
   private device: USBDevice|undefined = undefined;
-  private mainControllerCallback:
-      (_: {data: ConsumerPortResponse|RecordControllerMessage}) => void;
+  private recordShell?: AdbStream;
 
-  constructor(adb: Adb, mainControllerCallback: (_: {
-                          data: ConsumerPortResponse
-                        }) => void) {
-    this.mainControllerCallback = mainControllerCallback;
+  constructor(adb: Adb, consumerPortListener: Consumer) {
+    super(consumerPortListener);
     this.adb = adb;
   }
 
-  sendMessage(message: ConsumerPortResponse|RecordControllerMessage) {
-    this.mainControllerCallback({data: message});
-  }
-
-  sendErrorMessage(message: string) {
-    console.error('Error in adb record controller: ', message);
-    this.sendMessage({type: 'RecordControllerError', message});
-  }
-
-  sendStatus(status: string) {
-    this.sendMessage({type: 'RecordControllerStatus', status});
-  }
-
   handleCommand(method: string, params: Uint8Array) {
-    // TODO(nicomazz): after having implemented the connection to the consumer
-    // port socket through adb (on a real device), this class will be a simple
-    // proxy.
     switch (method) {
       case 'EnableTracing':
         this.enableTracing(params);
@@ -64,9 +51,11 @@
       case 'ReadBuffers':
         this.readBuffers();
         break;
+      case 'DisableTracing':
+        this.disableTracing();
+        break;
       case 'FreeBuffers':  // no-op
       case 'GetTraceStats':
-      case 'DisableTracing':
         break;
       default:
         this.sendErrorMessage(`Method not recognized: ${method}`);
@@ -74,12 +63,9 @@
     }
   }
 
-  async enableTracing(configProto: Uint8Array) {
+  async enableTracing(enableTracingProto: Uint8Array) {
     try {
-      if (this.state !== AdbState.READY) {
-        console.error('Current state of AdbRecordController is not READY');
-        return;
-      }
+      console.assert(this.state === AdbState.READY);
       this.device = await this.findDevice();
 
       if (this.device === undefined) {
@@ -89,9 +75,17 @@
       this.sendStatus(
           'Check the screen of your device and allow USB debugging.');
       await this.adb.connect(this.device);
-      await this.startRecording(configProto);
-      this.sendStatus('Recording in progress...');
+      const traceConfigProto = extractTraceConfig(enableTracingProto);
 
+      if (!traceConfigProto) {
+        this.sendErrorMessage('Invalid config.');
+        return;
+      }
+
+      await this.startRecording(traceConfigProto);
+      const duration = extractDurationFromTraceConfig(traceConfigProto);
+      this.sendStatus(`Recording in progress${
+          duration ? ' for ' + duration.toString() + ' ms' : ''}...`);
     } catch (e) {
       this.sendErrorMessage(e.message);
     }
@@ -100,15 +94,17 @@
   async startRecording(configProto: Uint8Array) {
     this.state = AdbState.RECORDING;
     const recordCommand = this.generateStartTracingCommand(configProto);
-    const recordShell: AdbStream = await this.adb.shell(recordCommand);
-    let response = '';
-    recordShell.onData = (str, _) => response += str;
-    recordShell.onClose = () => {
+    this.recordShell = await this.adb.shell(recordCommand);
+    const output: string[] = [];
+    this.recordShell.onData = (str, _) => output.push(str);
+    this.recordShell.onClose = () => {
+      const response = output.join();
       if (!this.tracingEndedSuccessfully(response)) {
         this.sendErrorMessage(response);
         this.state = AdbState.READY;
         return;
       }
+      this.sendStatus('Recording ended successfully. Fetching the trace..');
       this.sendMessage({type: 'EnableTracingResponse'});
     };
   }
@@ -138,6 +134,9 @@
       // things are not working, the chunks should be sent as they are received,
       // like in the following line.
       // this.sendMessage(this.generateChunkReadResponse(str));
+      // EDIT: we should send back a response as if it was a real
+      // ReadBufferResponse, with trace packets. Here we are only sending the
+      // trace split in several pieces.
       trace += str;
     };
     readTraceShell.onClose = () => {
@@ -145,10 +144,34 @@
 
       this.sendMessage(
           this.generateChunkReadResponse(decoded, /* last */ true));
+      this.adb.disconnect();
       this.state = AdbState.READY;
     };
   }
 
+  // TODO(nicomazz): Implement cancel/reset recording.
+  async disableTracing() {
+    console.assert(this.recordShell !== undefined);
+    if (!this.recordShell) return;
+
+    // We are not using 'pidof perfetto' so that we can use more filters. 'ps -u
+    // shell' is meant to catch processes started from shell, so if there are
+    // other ongoing tracing sessions started by others, we are not killing
+    // them.
+    const pid = await this.adb.shellOutputAsString(
+        `ps -u shell | grep perfetto | awk '{print $2}'`);
+    if (pid.length === 0 || isNaN(Number(pid))) {
+      this.sendErrorMessage(
+          'Unexpected error, impossible to stop the recording');
+      console.error('Perfetto pid not found. Command output: ', pid);
+      return;
+    }
+    // Perfetto stops and finalizes the tracing session on SIGINT.
+    const killOutput =
+        await this.adb.shellOutputAsString(`kill -SIGINT ${pid}`);
+    console.assert(killOutput.length === 0);
+  }
+
   generateChunkReadResponse(data: string, last = false): ReadBuffersResponse {
     return {
       type: 'ReadBuffersResponse',
diff --git a/ui/src/controller/adb_record_controller_jsdomtest.ts b/ui/src/controller/adb_record_controller_jsdomtest.ts
index dffb563..503f92d 100644
--- a/ui/src/controller/adb_record_controller_jsdomtest.ts
+++ b/ui/src/controller/adb_record_controller_jsdomtest.ts
@@ -14,14 +14,29 @@
 
 import {dingus} from 'dingusjs';
 
+import {perfetto} from '../gen/protos';
 import {AdbStream, MockAdb, MockAdbStream} from './adb_interfaces';
-import {AdbRecordController} from './adb_record_controller';
+import {AdbConsumerPort} from './adb_record_controller';
+import {Consumer} from './record_controller_interfaces';
 
-const mainCallback = jest.fn();
+function generateMockConsumer(): Consumer {
+  return {
+    onConsumerPortResponse: jest.fn(),
+    onError: jest.fn(),
+    onStatus: jest.fn()
+  };
+}
+const mainCallback = generateMockConsumer();
 const adbMock = new MockAdb();
-const adbController = new AdbRecordController(adbMock, mainCallback);
+const adbController = new AdbConsumerPort(adbMock, mainCallback);
 const mockIntArray = new Uint8Array();
 
+const enableTracingRequest = new perfetto.protos.EnableTracingRequest();
+enableTracingRequest.traceConfig = new perfetto.protos.TraceConfig();
+const enableTracingRequestProto =
+    perfetto.protos.EnableTracingRequest.encode(enableTracingRequest).finish();
+
+
 test('handleCommand', () => {
   adbController.findDevice = () => {
     return Promise.resolve(dingus<USBDevice>());
@@ -44,9 +59,9 @@
 });
 
 test('enableTracing', async () => {
-  const mainCallback = jest.fn();
+  const mainCallback = generateMockConsumer();
   const adbMock = new MockAdb();
-  const adbController = new AdbRecordController(adbMock, mainCallback);
+  const adbController = new AdbConsumerPort(adbMock, mainCallback);
 
   adbController.sendErrorMessage =
       jest.fn().mockImplementation(s => console.error(s));
@@ -71,13 +86,11 @@
 
   adbController.generateStartTracingCommand = (_) => 'CMD';
 
-  await adbController.enableTracing(mockIntArray);
+  await adbController.enableTracing(enableTracingRequestProto);
   expect(adbShell).toBeCalledWith('CMD');
   expect(findDevice).toHaveBeenCalledTimes(1);
   expect(connectToDevice).toHaveBeenCalledTimes(1);
-  // Two messages: RecordControllerStatus asking for allow the debug, and
-  // another status to clear that message.
-  expect(sendMessage).toHaveBeenCalledTimes(2);
+  expect(sendMessage).toHaveBeenCalledTimes(0);
 
 
   stream.onData('starting tracing Wrote 123 bytes', mockIntArray);
@@ -110,4 +123,4 @@
              'Connected to the Perfetto traced service, starting tracing for \
 0 ms'))
       .toBe(false);
-});
\ No newline at end of file
+});
diff --git a/ui/src/controller/chrome_proxy_record_controller.ts b/ui/src/controller/chrome_proxy_record_controller.ts
new file mode 100644
index 0000000..c2ce289
--- /dev/null
+++ b/ui/src/controller/chrome_proxy_record_controller.ts
@@ -0,0 +1,69 @@
+// 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 {uint8ArrayToString} from '../base/string_utils';
+
+import {ConsumerPortResponse, Typed} from './consumer_port_types';
+import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
+
+export interface ChromeExtensionError extends Typed {
+  error: string;
+}
+
+export interface ChromeExtensionStatus extends Typed {
+  status: string;
+}
+
+export type ChromeExtensionMessage =
+    ChromeExtensionError|ChromeExtensionStatus|ConsumerPortResponse;
+
+function isError(obj: Typed): obj is ChromeExtensionError {
+  return obj.type === 'ChromeExtensionError';
+}
+
+function isStatus(obj: Typed): obj is ChromeExtensionStatus {
+  return obj.type === 'ChromeExtensionStatus';
+}
+
+// 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.
+export class ChromeExtensionConsumerPort extends RpcConsumerPort {
+  private extensionPort: MessagePort;
+
+  constructor(extensionPort: MessagePort, consumerPortListener: Consumer) {
+    super(consumerPortListener);
+    this.extensionPort = extensionPort;
+    this.extensionPort.onmessage = this.onExtensionMessage.bind(this);
+  }
+
+  onExtensionMessage(message: {data: ChromeExtensionMessage}) {
+    if (isError(message.data)) {
+      this.sendErrorMessage(message.data.error);
+    } else if (isStatus(message.data)) {
+      this.sendStatus(message.data.status);
+    } else {
+      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});
+  }
+}
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 94f839d..062770b 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -28,8 +28,8 @@
 import {ControllerAny} from './controller';
 
 type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
-    'LegacyTrace'|'SliceDetails'|'Loading'|'Search'|'BufferUsage'|
-    'RecordingLog'|'SearchResult';
+    'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'Loading'|
+    'Search'|'BufferUsage'|'RecordingLog'|'SearchResult';
 
 export interface App {
   state: State;
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 34feb53..69a3f48 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -15,6 +15,7 @@
 import {ungzip} from 'pako';
 import {Message, Method, rpc, RPCImplCallback} from 'protobufjs';
 
+import {stringToUint8Array, uint8ArrayToBase64} from '../base/string_utils';
 import {Actions} from '../common/actions';
 import {
   AndroidLogConfig,
@@ -34,47 +35,27 @@
   isAndroidTarget,
   isChromeTarget,
   MAX_TIME,
-  RecordConfig
+  RecordConfig,
+  TargetOs
 } from '../common/state';
+import {perfetto} from '../gen/protos';
 
 import {AdbOverWebUsb} from './adb';
-import {AdbRecordController} from './adb_record_controller';
+import {AdbConsumerPort} from './adb_record_controller';
+import {ChromeExtensionConsumerPort} from './chrome_proxy_record_controller';
 import {
   ConsumerPortResponse,
   GetTraceStatsResponse,
   isEnableTracingResponse,
   isGetTraceStatsResponse,
   isReadBuffersResponse,
-  Typed,
 } from './consumer_port_types';
 import {Controller} from './controller';
 import {App, globals} from './globals';
+import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
 
 type RPCImplMethod = (Method|rpc.ServiceMethod<Message<{}>, Message<{}>>);
 
-export interface RecordControllerError extends Typed {
-  message: string;
-}
-
-export interface RecordControllerStatus extends Typed {
-  status: string;
-}
-
-export type RecordControllerMessage =
-    RecordControllerError|RecordControllerStatus;
-
-function isError(obj: Typed): obj is RecordControllerError {
-  return obj.type === 'RecordControllerError';
-}
-
-function isStatus(obj: Typed): obj is RecordControllerStatus {
-  return obj.type === 'RecordControllerStatus';
-}
-
-export function uint8ArrayToBase64(buffer: Uint8Array): string {
-  return btoa(String.fromCharCode.apply(null, Array.from(buffer)));
-}
-
 export function genConfigProto(uiCfg: RecordConfig): Uint8Array {
   return TraceConfig.encode(genConfig(uiCfg)).finish();
 }
@@ -150,12 +131,6 @@
     ftraceEvents.add('power/gpu_frequency');
   }
 
-  if (uiCfg.gpuSched) {
-    ftraceEvents.add('gpu/gpu_sched_enqueue');
-    ftraceEvents.add('gpu/gpu_sched_submit');
-    ftraceEvents.add('gpu/gpu_sched_complete');
-  }
-
   if (uiCfg.cpuSyscall) {
     ftraceEvents.add('raw_syscalls/sys_enter');
     ftraceEvents.add('raw_syscalls/sys_exit');
@@ -273,6 +248,10 @@
     protoCfg.dataSources.push(ds);
   }
 
+  if (uiCfg.chromeLogs) {
+    chromeCategories.add('log');
+  }
+
   if (uiCfg.taskScheduling) {
     chromeCategories.add('toplevel');
     chromeCategories.add('sequence_manager');
@@ -436,7 +415,28 @@
   return [...message(json, 0)].join('');
 }
 
-export class RecordController extends Controller<'main'> {
+export function extractTraceConfig(enableTracingRequest: Uint8Array):
+    Uint8Array|undefined {
+  try {
+    const enableTracingObject =
+        perfetto.protos.EnableTracingRequest.decode(enableTracingRequest);
+    if (!enableTracingObject.traceConfig) return undefined;
+    return perfetto.protos.TraceConfig.encode(enableTracingObject.traceConfig)
+        .finish();
+  } catch (e) {  // This catch is for possible proto encoding/decoding issues.
+    console.error('Error extracting the config: ', e.message);
+    return undefined;
+  }
+}
+
+export function extractDurationFromTraceConfig(traceConfigProto: Uint8Array) {
+  try {
+    return perfetto.protos.TraceConfig.decode(traceConfigProto).durationMs;
+  } catch (e) {  // This catch is for possible proto encoding/decoding issues.
+    return undefined;
+  }
+}
+export class RecordController extends Controller<'main'> implements Consumer {
   private app: App;
   private config: RecordConfig|null = null;
   private extensionPort: MessagePort;
@@ -445,14 +445,14 @@
   private traceBuffer = '';
   private bufferUpdateInterval: ReturnType<typeof setTimeout>|undefined;
 
-  private adbRecordController = new AdbRecordController(
-      new AdbOverWebUsb(), this.onConsumerPortMessage.bind(this));
+  // We have a different controller for each targetOS. The correct one will be
+  // created when needed, and stored here.
+  private controllers = new Map<TargetOs, RpcConsumerPort>();
   constructor(args: {app: App, extensionPort: MessagePort}) {
     super('main');
     this.app = args.app;
     this.consumerPort = ConsumerPort.create(this.rpcImpl.bind(this));
     this.extensionPort = args.extensionPort;
-    this.extensionPort.onmessage = this.onConsumerPortMessage.bind(this);
   }
 
   run() {
@@ -510,16 +510,14 @@
     this.consumerPort.readBuffers({});
   }
 
-  onConsumerPortMessage({data}: {
-    data: ConsumerPortResponse|RecordControllerMessage
-  }) {
+  onConsumerPortResponse(data: ConsumerPortResponse) {
     if (data === undefined) return;
-
     if (isReadBuffersResponse(data)) {
       if (!data.slices) return;
+      // TODO(nicomazz): handle this as intended by consumer_port.proto.
       this.traceBuffer += data.slices[0].data;
       // TODO(nicomazz): Stream the chunks directly in the trace processor.
-      if (data.slices[0].lastSliceForPacket) this.openTraceInUI();
+      if (data.slices[0].lastSliceForPacket) this.onTraceComplete();
     } else if (isEnableTracingResponse(data)) {
       this.readBuffers();
     } else if (isGetTraceStatsResponse(data)) {
@@ -527,77 +525,81 @@
       if (percentage) {
         globals.publish('BufferUsage', {percentage});
       }
-    } else if (isError(data)) {
-      this.handleError(data.message);
-    } else if (isStatus(data)) {
-      this.handleStatus(data.status);
+    } else {
+      console.error('Unrecognized consumer port response:', data);
     }
   }
 
-  openTraceInUI() {
+  onTraceComplete() {
     this.consumerPort.freeBuffers({});
     globals.dispatch(Actions.setRecordingStatus({status: undefined}));
-    const trace = ungzip(this.stringToArrayBuffer(this.traceBuffer));
+    if (globals.state.recordingCancelled) {
+      globals.dispatch(
+          Actions.setLastRecordingError({error: 'Recording cancelled.'}));
+      return;
+    }
+    const trace = ungzip(stringToUint8Array(this.traceBuffer));
     globals.dispatch(Actions.openTraceFromBuffer({buffer: trace.buffer}));
     this.traceBuffer = '';
   }
 
-  stringToArrayBuffer(str: string): Uint8Array {
-    const buf = new ArrayBuffer(str.length);
-    const bufView = new Uint8Array(buf);
-    for (let i = 0, strLen = str.length; i < strLen; i++) {
-      bufView[i] = str.charCodeAt(i);
-    }
-    return bufView;
-  }
-
 
   getBufferUsagePercentage(data: GetTraceStatsResponse): number {
     if (!data.traceStats || !data.traceStats.bufferStats) return 0.0;
-    let used = 0.0, total = 0.0;
+    let maximumUsage = 0;
     for (const buffer of data.traceStats.bufferStats) {
-      used += buffer.bytesWritten as number;
-      total += buffer.bufferSize as number;
+      const used = buffer.bytesWritten as number;
+      const total = buffer.bufferSize as number;
+      maximumUsage = Math.max(maximumUsage, used / total);
     }
-    if (total === 0.0) return 0;
-    return used / total;
+    return maximumUsage;
   }
 
-  handleError(message: string) {
+  onError(message: string) {
     globals.dispatch(
         Actions.setLastRecordingError({error: message.substr(0, 150)}));
     globals.dispatch(Actions.stopRecording({}));
   }
 
-  handleStatus(message: string) {
+  onStatus(message: string) {
     globals.dispatch(Actions.setRecordingStatus({status: message}));
   }
 
   // Depending on the recording target, different implementation of the
   // consumer_port will be used.
   // - Chrome target: This forwards the messages that have to be sent
-  // to the extension to the frontend. This is necessary because this controller
-  // is running in a separate worker, that can't directly send messages to the
-  // extension.
+  // to the extension to the frontend. This is necessary because this
+  // controller is running in a separate worker, that can't directly send
+  // messages to the extension.
   // - Android device target: WebUSB is used to communicate using the adb
-  // protocol. Actually, there is no full consumer_port implementation, but only
-  // the support to start tracing and fetch the file.
+  // protocol. Actually, there is no full consumer_port implementation, but
+  // only the support to start tracing and fetch the file.
+  getTargetController(target: TargetOs): RpcConsumerPort {
+    let controller = this.controllers.get(target);
+    if (controller) return controller;
+
+    if (isChromeTarget(target)) {
+      controller = new ChromeExtensionConsumerPort(this.extensionPort, this);
+    } else if (isAndroidTarget(target)) {
+      // TODO(nicomazz): create the correct controller also based on the
+      // selected android device.
+      controller = new AdbConsumerPort(new AdbOverWebUsb(), this);
+    }
+
+    if (!controller) throw Error(`Unknown target: ${target}`);
+
+    this.controllers.set(target, controller);
+    return controller;
+  }
+
   private rpcImpl(
       method: RPCImplMethod, requestData: Uint8Array,
       _callback: RPCImplCallback) {
-    const target = this.app.state.recordConfig.targetOS;
-    if (isChromeTarget(target) && method !== null && method.name !== null &&
-        this.config !== null) {
-      this.extensionPort.postMessage(
-          {method: method.name, traceConfig: requestData});
-    } else if (isAndroidTarget(target)) {
-      // TODO(nicomazz): In theory requestData should contain the configuration
-      // proto, but in practice there are missing fields. As a temporary
-      // workaround I'm directly passing the configuration.
-      this.adbRecordController.handleCommand(
-          method.name, genConfigProto(this.config!));
-    } else {
-      console.error(`Target ${target} not supported!`);
+    try {
+      this.getTargetController(this.app.state.recordConfig.targetOS)
+          .handleCommand(method.name, requestData);
+    } catch (e) {
+      console.error(`error invoking ${method}: ${e.message}`);
     }
   }
 }
diff --git a/ui/src/controller/record_controller_interfaces.ts b/ui/src/controller/record_controller_interfaces.ts
new file mode 100644
index 0000000..afd1d8b
--- /dev/null
+++ b/ui/src/controller/record_controller_interfaces.ts
@@ -0,0 +1,36 @@
+import {ConsumerPortResponse} from './consumer_port_types';
+
+export type ConsumerPortCallback = (_: ConsumerPortResponse) => void;
+export type ErrorCallback = (_: string) => void;
+export type StatusCallback = (_: string) => void;
+
+export abstract class RpcConsumerPort {
+  // The responses of the call invocations should be sent through this listener.
+  // This is done by the 3 "send" methods in this abstract class.
+  private consumerPortListener: Consumer;
+
+  constructor(consumerPortListener: Consumer) {
+    this.consumerPortListener = consumerPortListener;
+  }
+
+  // RequestData is the proto representing the arguments of the function call.
+  abstract handleCommand(methodName: string, requestData: Uint8Array): void;
+
+  sendMessage(data: ConsumerPortResponse) {
+    this.consumerPortListener.onConsumerPortResponse(data);
+  }
+
+  sendErrorMessage(message: string) {
+    this.consumerPortListener.onError(message);
+  }
+
+  sendStatus(status: string) {
+    this.consumerPortListener.onStatus(status);
+  }
+}
+
+export interface Consumer {
+  onConsumerPortResponse(data: ConsumerPortResponse): void;
+  onError: ErrorCallback;
+  onStatus: StatusCallback;
+}
\ No newline at end of file
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index 3455b52..8fb9983 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -19,18 +19,7 @@
 import {createEmptyRecordConfig, RecordConfig} from '../common/state';
 
 import {App} from './globals';
-import {
-  genConfigProto,
-  RecordController,
-  toPbtxt,
-  uint8ArrayToBase64
-} from './record_controller';
-
-test('uint8ArrayToBase64', () => {
-  const bytes = [...'Hello, world'].map(c => c.charCodeAt(0));
-  const buffer = new Uint8Array(bytes);
-  expect(uint8ArrayToBase64(buffer)).toEqual('SGVsbG8sIHdvcmxk');
-});
+import {genConfigProto, RecordController, toPbtxt} from './record_controller';
 
 test('encodeConfig', () => {
   const config = createEmptyRecordConfig();
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 8738d63..3e02125 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -65,7 +65,8 @@
 
     const visibleState = this.app.state.frontendLocalState.visibleState;
     const omniboxState = this.app.state.frontendLocalState.omniboxState;
-    if (visibleState === undefined || omniboxState === undefined) {
+    if (visibleState === undefined || omniboxState === undefined ||
+        omniboxState.mode === 'COMMAND') {
       return;
     }
     const newSpan = new TimeSpan(visibleState.startSec, visibleState.endSec);
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 52068c3..9e881b7 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -13,8 +13,12 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
-import {fromNs} from '../common/time';
-import {SliceDetails} from '../frontend/globals';
+import {fromNs, toNs} from '../common/time';
+import {
+  CounterDetails,
+  HeapDumpDetails,
+  SliceDetails
+} from '../frontend/globals';
 
 import {Controller} from './controller';
 import {globals} from './globals';
@@ -26,7 +30,7 @@
 // This class queries the TP for the details on a specific slice that has
 // been clicked.
 export class SelectionController extends Controller<'main'> {
-  private lastSelectedSlice?: number;
+  private lastSelectedId?: number;
   private lastSelectedKind?: string;
   constructor(private args: SelectionControllerArgs) {
     super('main');
@@ -35,27 +39,50 @@
   run() {
     const selection = globals.state.currentSelection;
     if (selection === null ||
-        (selection.kind !== 'SLICE' && selection.kind !== 'CHROME_SLICE') ||
-        (selection.id === this.lastSelectedSlice &&
-         selection.kind === this.lastSelectedKind)) {
+        (selection.kind !== 'SLICE' && selection.kind !== 'CHROME_SLICE' &&
+         selection.kind !== 'COUNTER' && selection.kind !== 'HEAP_DUMP') ||
+        (selection.id === this.lastSelectedId &&
+         selection.kind === this.lastSelectedKind &&
+         selection.kind !== 'COUNTER')) {
       return;
     }
-    const selectedSlice = selection.id;
-    const selectedSliceKind = selection.kind;
-    this.lastSelectedSlice = selectedSlice;
-    this.lastSelectedKind = selectedSliceKind;
+    const selectedId = selection.id;
+    const selectedKind = selection.kind;
+    this.lastSelectedId = selectedId;
+    this.lastSelectedKind = selectedKind;
 
-    if (selectedSlice === undefined) return;
+    if (selectedId === undefined) return;
 
-    if (selectedSliceKind === 'SLICE') {
+    if (selection.kind === 'HEAP_DUMP') {
+      const selected: HeapDumpDetails = {};
+      const ts = selection.ts;
+      const upid = selection.upid;
+      this.heapDumpDetails(ts, upid).then(results => {
+        if (results !== undefined && selection &&
+            selection.kind === selectedKind && selection.id === selectedId) {
+          Object.assign(selected, results);
+          globals.publish('HeapDumpDetails', selected);
+        }
+      });
+    } else if (selection.kind === 'COUNTER') {
+      const selected: CounterDetails = {};
+      this.counterDetails(selection.leftTs, selection.rightTs, selection.id)
+          .then(results => {
+            if (results !== undefined && selection &&
+                selection.kind === selectedKind &&
+                selection.id === selectedId) {
+              Object.assign(selected, results);
+              globals.publish('CounterDetails', selected);
+            }
+          });
+    } else if (selectedKind === 'SLICE') {
       const sqlQuery = `SELECT ts, dur, priority, end_state, utid FROM sched
-                        WHERE row_id = ${selectedSlice}`;
+                        WHERE row_id = ${selectedId}`;
       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 &&
-            selection.kind === selectedSliceKind &&
-            selection.id === selectedSlice) {
+            selection.kind === selectedKind && selection.id === selectedId) {
           const ts = result.columns[0].longValues![0] as number;
           const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
           const dur = fromNs(result.columns[1].longValues![0] as number);
@@ -70,19 +97,18 @@
           });
         }
       });
-    } else if (selectedSliceKind === 'CHROME_SLICE') {
-      if (selectedSlice === -1) {
+    } else if (selectedKind === 'CHROME_SLICE') {
+      if (selectedId === -1) {
         globals.publish('SliceDetails', {ts: 0, name: 'Summarized slice'});
         return;
       }
       const sqlQuery = `SELECT ts, dur, name, cat FROM slices
-      WHERE slice_id = ${selectedSlice}`;
+      WHERE slice_id = ${selectedId}`;
       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 &&
-            selection.kind === selectedSliceKind &&
-            selection.id === selectedSlice) {
+            selection.kind === selectedKind && selection.id === selectedId) {
           const ts = result.columns[0].longValues![0] as number;
           const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
           const name = result.columns[2].stringValues![0];
@@ -97,6 +123,38 @@
     }
   }
 
+  async heapDumpDetails(ts: number, upid: number) {
+    const allocatedMemory = await this.args.engine.query(
+        `select sum(size) from heap_profile_allocation where ts <= ${
+            ts} and size > 0 and upid = ${upid}`);
+    const allocated = allocatedMemory.columns[0].longValues![0];
+    const allocatedNotFreedMemory = await this.args.engine.query(
+        `select sum(size) from heap_profile_allocation where ts <= ${
+            ts} and upid = ${upid}`);
+    const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
+    const startTime = fromNs(ts) - globals.state.traceTime.startSec;
+    return {ts: startTime, allocated, allocatedNotFreed};
+  }
+
+  async counterDetails(ts: number, rightTs: number, id: number) {
+    const counter = await this.args.engine.query(
+        `SELECT value FROM counter_values WHERE ts = ${ts} AND counter_id = ${
+            id}`);
+    const value = counter.columns[0].doubleValues![0];
+    // Finding previous value. If there isn't previous one, it will return 0 for
+    // ts and value.
+    const previous = await this.args.engine.query(
+        `SELECT MAX(ts), value FROM counter_values WHERE ts < ${
+            ts} and counter_id = ${id}`);
+    const previousValue = previous.columns[1].doubleValues![0];
+    const endTs =
+        rightTs !== -1 ? rightTs : toNs(globals.state.traceTime.endSec);
+    const delta = value - previousValue;
+    const duration = endTs - ts;
+    const startTime = fromNs(ts) - globals.state.traceTime.startSec;
+    return {startTime, value, delta, duration};
+  }
+
   async schedulingDetails(ts: number, utid: number|Long) {
     let event = 'sched_waking';
     const waking = await this.args.engine.query(
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index bf2376e..34a79a4 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -33,6 +33,7 @@
 import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq/common';
 import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
 import {GPU_FREQ_TRACK_KIND} from '../tracks/gpu_freq/common';
+import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile/common';
 import {
   PROCESS_SCHEDULING_TRACK_KIND
 } from '../tracks/process_scheduling/common';
@@ -162,11 +163,11 @@
       }
     } else if (engineCfg.source instanceof ArrayBuffer) {
       this.updateStatus(`${statusHeader} 0 %`);
-      const buffer = engineCfg.source;
+      const buffer = new Uint8Array(engineCfg.source);
       const SLICE_SIZE = 1024 * 1024;
       for (let off = 0; off < buffer.byteLength; off += SLICE_SIZE) {
-        const slice = buffer.slice(off, off + SLICE_SIZE);
-        await this.engine.parse(new Uint8Array(slice));
+        const slice = buffer.subarray(off, off + SLICE_SIZE);
+        await this.engine.parse(slice);
         const progress =
             Math.round((off + slice.byteLength) / buffer.byteLength * 100);
         this.updateStatus(`${statusHeader} ${progress} %`);
@@ -217,8 +218,9 @@
 
     // We don't know the resolution at this point. However this will be
     // replaced in 50ms so a guess is fine.
+    const resolution = (traceTime.end - traceTime.start) / 1000;
     actions.push(Actions.setVisibleTraceTime(
-        {...traceTimeState, lastUpdate: Date.now() / 1000, resolution: 0.008}));
+        {...traceTimeState, lastUpdate: Date.now() / 1000, resolution}));
 
     globals.dispatchMultiple(actions);
 
@@ -305,6 +307,15 @@
       }
     }
 
+    const heapProfiles = await engine.query(`
+      select distinct(upid) from heap_profile_allocation`);
+
+    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 counters
@@ -492,6 +503,16 @@
               });
             });
           }
+
+          if (heapUpids.has(upid)) {
+            tracksToAdd.push({
+              engineId: this.engineId,
+              kind: HEAP_PROFILE_TRACK_KIND,
+              name: `Heap Profile`,
+              trackGroup: pUuid,
+              config: {upid}
+            });
+          }
         }
       }
       const counterThreadNames = counterUtids[utid];
diff --git a/ui/src/frontend/counter_panel.ts b/ui/src/frontend/counter_panel.ts
new file mode 100644
index 0000000..458a037
--- /dev/null
+++ b/ui/src/frontend/counter_panel.ts
@@ -0,0 +1,58 @@
+// Copyright (C) 2019 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 {fromNs, timeToCode} from '../common/time';
+
+import {globals} from './globals';
+import {Panel} from './panel';
+
+interface CounterDetailsPanelAttrs {}
+
+export class CounterDetailsPanel extends Panel<CounterDetailsPanelAttrs> {
+  view() {
+    const counterInfo = globals.counterDetails;
+    if (counterInfo && counterInfo.startTime &&
+        counterInfo.value !== undefined && counterInfo.delta !== undefined &&
+        counterInfo.duration !== undefined) {
+      return m(
+          '.details-panel',
+          m('.details-panel-heading', `Counter Details:`),
+          m(
+              '.details-table',
+              [m('table',
+                 [
+                   m('tr',
+                     m('th', `Start time`),
+                     m('td', `${timeToCode(counterInfo.startTime)}`)),
+                   m('tr',
+                     m('th', `Value`),
+                     m('td', `${counterInfo.value.toLocaleString()}`)),
+                   m('tr',
+                     m('th', `Delta`),
+                     m('td', `${counterInfo.delta.toLocaleString()}`)),
+                   m('tr',
+                     m('th', `Duration`),
+                     m('td', `${timeToCode(fromNs(counterInfo.duration))}`)),
+                 ])],
+              ));
+    } else {
+      return m(
+          '.details-panel', m('.details-panel-heading', `Counter Details:`));
+    }
+  }
+
+  renderCanvas() {}
+}
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index e956f2b..8a1a6ed 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -61,6 +61,20 @@
   };
 }
 
+// Calculate the space a scrollbar takes up so that we can subtract it from
+// the canvas width.
+function calculateScrollbarWidth() {
+  const outer = document.createElement('div');
+  outer.style.overflowY = 'scroll';
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+  document.body.appendChild(outer);
+  const width =
+      outer.getBoundingClientRect().width - inner.getBoundingClientRect().width;
+  document.body.removeChild(outer);
+  return width;
+}
+
 /**
  * State that is shared between several frontend components, but not the
  * controller. This state is updated at 60fps.
@@ -80,10 +94,12 @@
   visibleTracks = new Set<string>();
   prevVisibleTracks = new Set<string>();
   searchIndex = -1;
+  private scrollBarWidth: undefined|number = undefined;
 
   private _omniboxState: OmniboxState = {
     lastUpdate: 0,
     omnibox: '',
+    mode: 'SEARCH',
   };
 
   private _visibleState: VisibleState = {
@@ -97,6 +113,13 @@
   // and a |timeScale| have a notion of time range. That should live in one
   // place only.
 
+  getScrollbarWidth() {
+    if (this.scrollBarWidth === undefined) {
+      this.scrollBarWidth = calculateScrollbarWidth();
+    }
+    return this.scrollBarWidth;
+  }
+
   togglePerfDebug() {
     this.perfDebug = !this.perfDebug;
     globals.rafScheduler.scheduleFullRedraw();
@@ -163,16 +186,19 @@
   mergeState(state: FrontendState): void {
     this._omniboxState = chooseLastest(this._omniboxState, state.omniboxState);
     this._visibleState = chooseLastest(this._visibleState, state.visibleState);
-    this.updateLocalTime(
-        new TimeSpan(this._visibleState.startSec, this._visibleState.endSec));
+    if (this._visibleState === state.visibleState) {
+      this.updateLocalTime(
+          new TimeSpan(this._visibleState.startSec, this._visibleState.endSec));
+    }
   }
 
   private debouncedSetOmnibox = debounce(() => {
     globals.dispatch(Actions.setOmnibox(this._omniboxState));
   }, 20);
 
-  set omnibox(value: string) {
+  setOmnibox(value: string, mode: 'SEARCH'|'COMMAND') {
     this._omniboxState.omnibox = value;
+    this._omniboxState.mode = mode;
     this._omniboxState.lastUpdate = Date.now() / 1000;
     this.debouncedSetOmnibox();
   }
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 24038bc..1d44b88 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -35,6 +35,19 @@
   name?: string;
 }
 
+export interface CounterDetails {
+  startTime?: number;
+  value?: number;
+  delta?: number;
+  duration?: number;
+}
+
+export interface HeapDumpDetails {
+  ts?: number;
+  allocated?: number;
+  allocatedNotFreed?: number;
+}
+
 export interface QuantizedLoad {
   startSec: number;
   endSec: number;
@@ -67,6 +80,8 @@
   private _overviewStore?: OverviewStore = undefined;
   private _threadMap?: ThreadMap = undefined;
   private _sliceDetails?: SliceDetails = undefined;
+  private _counterDetails?: CounterDetails = undefined;
+  private _heapDumpDetails?: HeapDumpDetails = undefined;
   private _isLoading = false;
   private _bufferUsage?: number = undefined;
   private _recordingLog?: string = undefined;
@@ -95,6 +110,8 @@
     this._overviewStore = new Map<string, QuantizedLoad[]>();
     this._threadMap = new Map<number, ThreadDesc>();
     this._sliceDetails = {};
+    this._counterDetails = {};
+    this._heapDumpDetails = {};
   }
 
   get state(): State {
@@ -142,6 +159,22 @@
     this._sliceDetails = assertExists(click);
   }
 
+  get counterDetails() {
+    return assertExists(this._counterDetails);
+  }
+
+  set counterDetails(click: CounterDetails) {
+    this._counterDetails = assertExists(click);
+  }
+
+  get heapDumpDetails() {
+    return assertExists(this._heapDumpDetails);
+  }
+
+  set heapDumpDetails(click: HeapDumpDetails) {
+    this._heapDumpDetails = assertExists(click);
+  }
+
   set loading(isLoading: boolean) {
     this._isLoading = isLoading;
   }
@@ -185,6 +218,12 @@
     return Math.pow(2, Math.floor(Math.log2(resolution)));
   }
 
+  makeSelection(action: DeferredAction<{}>) {
+    // A new selection should cancel the current search selection.
+    globals.frontendLocalState.searchIndex = -1;
+    globals.dispatch(action);
+  }
+
   resetForTesting() {
     this._dispatch = undefined;
     this._state = undefined;
diff --git a/ui/src/frontend/heap_dump_panel.ts b/ui/src/frontend/heap_dump_panel.ts
new file mode 100644
index 0000000..d6c14e1
--- /dev/null
+++ b/ui/src/frontend/heap_dump_panel.ts
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 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 {timeToCode} from '../common/time';
+
+import {globals} from './globals';
+import {Panel} from './panel';
+
+interface HeapDumpDetailsPanelAttrs {}
+
+export class HeapDumpDetailsPanel extends Panel<HeapDumpDetailsPanelAttrs> {
+  view() {
+    const heapDumpInfo = globals.heapDumpDetails;
+    if (heapDumpInfo && heapDumpInfo.ts && heapDumpInfo.allocated &&
+        heapDumpInfo.allocatedNotFreed) {
+      return m(
+          '.details-panel',
+          m('.details-panel-heading', `Heap Snapshot Details:`),
+          m(
+              '.details-table',
+              [m('table',
+                 [
+                   m('tr',
+                     m('th', `Snapshot time`),
+                     m('td', `${timeToCode(heapDumpInfo.ts)}`)),
+                   m('tr',
+                     m('th', `Total allocated:`),
+                     m('td',
+                       `${heapDumpInfo.allocated.toLocaleString()} bytes`)),
+                   m('tr',
+                     m('th', `Allocated not freed:`),
+                     m('td',
+                       `${
+                           heapDumpInfo.allocatedNotFreed
+                               .toLocaleString()} bytes`)),
+                 ])],
+              ));
+    } else {
+      return m(
+          '.details-panel',
+          m('.details-panel-heading', `Heap Snapshot Details:`));
+    }
+  }
+
+  renderCanvas() {}
+}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 132b179..bf59470 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -29,7 +29,14 @@
 } from '../common/logs';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 
-import {globals, QuantizedLoad, SliceDetails, ThreadDesc} from './globals';
+import {
+  CounterDetails,
+  globals,
+  HeapDumpDetails,
+  QuantizedLoad,
+  SliceDetails,
+  ThreadDesc
+} from './globals';
 import {HomePage} from './home_page';
 import {openBufferWithLegacyTraceViewer} from './legacy_trace_viewer';
 import {postMessageHandler} from './post_message_handler';
@@ -111,6 +118,16 @@
     this.redraw();
   }
 
+  publishCounterDetails(click: CounterDetails) {
+    globals.counterDetails = click;
+    this.redraw();
+  }
+
+  publishHeapDumpDetails(click: HeapDumpDetails) {
+    globals.heapDumpDetails = click;
+    this.redraw();
+  }
+
   publishLoading(loading: boolean) {
     globals.loading = loading;
     globals.rafScheduler.scheduleRedraw();
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index c687d9e..e1ac1dc 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -16,10 +16,11 @@
 
 import {globals} from './globals';
 import {toggleHelp} from './help_modal';
+import {executeSearch} from './search_handler';
 
 // Handles all key events than are not handled by the
 // pan and zoom handler.
-export function handleKey(key: string, down: boolean) {
+export function handleKey(key: string, down: boolean, isShiftDown: boolean) {
   if (down && 'm' === key) {
     selectSliceSpan();
   }
@@ -40,6 +41,9 @@
   if (down && '?' === key) {
     toggleHelp();
   }
+  if (down && 'Enter' === key) {
+    executeSearch(isShiftDown);
+  }
 }
 
 function selectSliceSpan() {
@@ -60,6 +64,6 @@
   }
 
   if (startTs !== -1 && endTs !== -1) {
-    globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
+    globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
   }
 }
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index 326117e..9920e98 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -173,7 +173,7 @@
         if (note.isMovie) {
           globals.frontendLocalState.setVidTimestamp(note.timestamp);
         }
-        globals.dispatch(Actions.selectNote({id: note.id}));
+        globals.makeSelection(Actions.selectNote({id: note.id}));
         return;
       }
     }
diff --git a/ui/src/frontend/pan_and_zoom_handler.ts b/ui/src/frontend/pan_and_zoom_handler.ts
index fe93caa..f516ee5 100644
--- a/ui/src/frontend/pan_and_zoom_handler.ts
+++ b/ui/src/frontend/pan_and_zoom_handler.ts
@@ -184,7 +184,7 @@
     }
 
     // Handle key events that are not pan or zoom.
-    handleKey(e.key, true);
+    handleKey(e.key, true, this.shiftDown);
   }
 
   private onKeyUp(e: KeyboardEvent) {
@@ -201,7 +201,7 @@
     }
 
     // Handle key events that are not pan or zoom.
-    handleKey(e.key, false);
+    handleKey(e.key, false, this.shiftDown);
   }
 
   private updateShift(down: boolean) {
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 9b3a7e6..80f99c2 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -171,7 +171,12 @@
     const canvas = assertExists(ctx.canvas);
     canvas.style.height = `${this.canvasHeight}px`;
     const dpr = window.devicePixelRatio;
-    ctx.canvas.width = this.parentWidth * dpr;
+    // On non-MacOS if there is a solid scroll bar it can cover important
+    // pixels, reduce the size of the canvas so it doesn't overlap with
+    // the scroll bar.
+    ctx.canvas.width =
+        (this.parentWidth - globals.frontendLocalState.getScrollbarWidth()) *
+        dpr;
     ctx.canvas.height = this.canvasHeight * dpr;
     ctx.scale(dpr, dpr);
   }
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index f11e74a..ac9758a 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -214,13 +214,6 @@
         descr: 'Records gpu frequency via ftrace',
         setEnabled: (cfg, val) => cfg.gpuFreq = val,
         isEnabled: (cfg) => cfg.gpuFreq
-      } as ProbeAttrs),
-      m(Probe, {
-        title: 'GPU scheduling',
-        img: 'rec_cpu_wakeup.png',
-        descr: 'Records gpu scheduling via ftrace',
-        setEnabled: (cfg, val) => cfg.gpuSched = val,
-        isEnabled: (cfg) => cfg.gpuSched
       } as ProbeAttrs));
 }
 
@@ -481,6 +474,13 @@
         setEnabled: (cfg, val) => cfg.navigationAndLoading = val,
         isEnabled: (cfg) => cfg.navigationAndLoading
       } as ProbeAttrs),
+      m(Probe, {
+        title: 'Chrome Logs',
+        img: null,
+        descr: `Records Chrome log messages`,
+        setEnabled: (cfg, val) => cfg.chromeLogs = val,
+        isEnabled: (cfg) => cfg.chromeLogs
+      } as ProbeAttrs),
       ChromeCategoriesSelection());
 }
 
@@ -494,14 +494,18 @@
   // Show "disabled-by-default" categories last.
   const categoriesMap = new Map<string, string>();
   const disabledByDefaultCategories: string[] = [];
+  const disabledPrefix = 'disabled-by-default-';
   categories.forEach(cat => {
-    if (cat.startsWith('disabled')) {
+    if (cat.startsWith(disabledPrefix)) {
       disabledByDefaultCategories.push(cat);
     } else {
       categoriesMap.set(cat, cat);
     }
   });
-  disabledByDefaultCategories.forEach(cat => categoriesMap.set(cat, cat));
+  disabledByDefaultCategories.forEach(cat => {
+    categoriesMap.set(
+        cat, `${cat.replace(disabledPrefix, '')} (high overhead)`);
+  });
   return m(Dropdown, {
     title: 'Additional Chrome categories',
     cssClass: '.multicolumn.two-columns.chrome-categories',
@@ -642,6 +646,7 @@
       m('header', 'Instructions'),
       RecordingSnippet(),
       BufferUsageProgressBar(),
+      m('.buttons', StopCancelButtons()),
       recordingLog());
 }
 
@@ -768,11 +773,14 @@
   const realDeviceTarget = state.androidDeviceConnected !== undefined;
   const recInProgress = state.recordingInProgress;
 
-  const startButton =
-      m(`button${recInProgress ? '.selected' : ''}`,
-        {onclick: onStartRecordingPressed},
+  const start =
+      m(`button`,
+        {
+          class: recInProgress ? '' : 'selected',
+          onclick: onStartRecordingPressed
+        },
         'Start Recording');
-  const showCmdButton =
+  const showCmd =
       m(`button`,
         {
           onclick: () => {
@@ -781,28 +789,38 @@
           }
         },
         'Show Command');
-  const stopButton =
-      m(`button${recInProgress ? '' : '.disabled'}`,
-        {onclick: () => globals.dispatch(Actions.stopRecording({}))},
-        'Stop Recording');
 
   const buttons: m.Children = [];
 
   const targetOs = state.recordConfig.targetOS;
   if (isAndroidTarget(targetOs)) {
-    buttons.push(showCmdButton);
-    if (realDeviceTarget) buttons.push(startButton);
-    // TODO(nicomazz): Support stop recording on Android devices.
+    buttons.push(showCmd);
+    if (realDeviceTarget) buttons.push(start);
   } else if (isChromeTarget(targetOs) && state.extensionInstalled) {
-    buttons.push(startButton);
-    if (recInProgress) buttons.push(stopButton);
+    buttons.push(start);
   } else if (isLinuxTarget(targetOs)) {
-    buttons.push(showCmdButton);
+    buttons.push(showCmd);
   }
 
   return m('.button', buttons);
 }
 
+function StopCancelButtons() {
+  if (!globals.state.recordingInProgress) return [];
+
+  const stop =
+      m(`button.selected`,
+        {onclick: () => globals.dispatch(Actions.stopRecording({}))},
+        'Stop');
+
+  const cancel =
+      m(`button`,
+        {onclick: () => globals.dispatch(Actions.cancelRecording({}))},
+        'Cancel');
+
+  return [stop, cancel];
+}
+
 function onStartRecordingPressed() {
   location.href = '#!/record?p=instructions';
   globals.rafScheduler.scheduleFullRedraw();
@@ -822,7 +840,7 @@
 function ErrorLabel() {
   const lastRecordingError = globals.state.lastRecordingError;
   if (!lastRecordingError) return [];
-  return m('label.error-label', `⚠️ Error:  ${lastRecordingError}`);
+  return m('label.error-label', `Error:  ${lastRecordingError}`);
 }
 
 function recordingLog() {
@@ -902,7 +920,7 @@
           m(`li${routePage === 'gpu' ? '.active' : ''}`,
             m('i.material-icons', 'aspect_ratio'),
             m('.title', 'GPU'),
-            m('.sub', 'GPU frequency, scheduling'))),
+            m('.sub', 'GPU frequency'))),
         m('a[href="#!/record?p=power"]',
           m(`li${routePage === 'power' ? '.active' : ''}`,
             m('i.material-icons', 'battery_charging_full'),
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
new file mode 100644
index 0000000..3343577
--- /dev/null
+++ b/ui/src/frontend/search_handler.ts
@@ -0,0 +1,83 @@
+// 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 {searchSegment} from '../base/binary_search';
+import {Actions} from '../common/actions';
+import {fromNs, TimeSpan, toNs} from '../common/time';
+
+import {globals} from './globals';
+
+export function executeSearch(reverse = false) {
+  const state = globals.frontendLocalState;
+  const index = state.searchIndex;
+  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  const currentTs = globals.currentSearchResults.tsStarts[index];
+
+  // If this is a new search or the currentTs is not in the viewport,
+  // select the first/last item in the viewport.
+  if (index === -1 || currentTs < startNs || currentTs > endNs) {
+    if (reverse) {
+      const [smaller,] =
+        searchSegment(globals.currentSearchResults.tsStarts, endNs);
+      globals.frontendLocalState.setSearchIndex(smaller);
+    } else {
+      const [, larger] =
+          searchSegment(globals.currentSearchResults.tsStarts, startNs);
+      globals.frontendLocalState.setSearchIndex(larger);
+    }
+    // If there is no result in the current viewport, move it.
+    const currentTs = globals.currentSearchResults.tsStarts[state.searchIndex];
+    if (currentTs < startNs || currentTs > endNs) {
+      moveViewportToCurrentSearch();
+    }
+  } else {
+    // If the currentTs is in the viewport, increment the index and move the
+    // viewport if necessary.
+    if (reverse) {
+      globals.frontendLocalState.setSearchIndex(Math.max(index - 1, 0));
+    } else {
+      globals.frontendLocalState.setSearchIndex(Math.min(
+          index + 1, globals.currentSearchResults.sliceIds.length - 1));
+    }
+    moveViewportToCurrentSearch();
+  }
+  selectCurrentSearchResult();
+}
+
+function moveViewportToCurrentSearch() {
+  // Move viewport if our selection moves outside.
+  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
+  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  const currentTs = globals.currentSearchResults
+                        .tsStarts[globals.frontendLocalState.searchIndex];
+  const currentViewNs = endNs - startNs;
+  if (currentTs < startNs || currentTs > endNs) {
+    // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
+    globals.frontendLocalState.updateVisibleTime(new TimeSpan(
+        fromNs(currentTs - currentViewNs / 2),
+        fromNs(currentTs + currentViewNs / 2)));
+  }
+}
+
+function selectCurrentSearchResult() {
+  const state = globals.frontendLocalState;
+  const currentId = globals.currentSearchResults.sliceIds[state.searchIndex];
+  if (currentId !== undefined) {
+    globals.dispatch(Actions.selectSlice({
+      utid: globals.currentSearchResults.utids[state.searchIndex],
+      id: currentId
+    }));
+  }
+}
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index 89ce3fb..5005b1c 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -32,19 +32,24 @@
     ctx.fillStyle = '#999';
 
     // Write trace offset time + line.
-    ctx.textAlign = 'right';
     ctx.font = '12px Google Sans';
+
+    ctx.textAlign = 'right';
     const offsetTime =
         timeToString(range.start - globals.state.traceTime.startSec);
     ctx.fillText(offsetTime, TRACK_SHELL_WIDTH - 6, 11);
-    ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
+
+    ctx.textAlign = 'left';
+    const startTime = timeToString(globals.state.traceTime.startSec);
+    ctx.fillText(startTime + ' +', 6, 11);
 
     // Draw time axis.
     ctx.font = '10px Google Sans';
-    ctx.textAlign = 'left';
     for (const [x, time] of gridlines(size.width, range, timeScale)) {
       ctx.fillRect(x, 0, 1, size.height);
       ctx.fillText('+' + timeToString(time - range.start), x + 5, 10);
     }
+
+    ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
   }
 }
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index cc5080a..cc899db 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -80,6 +80,35 @@
   ctx.fillText(label, labelXLeft, yMid);
 }
 
+function drawIBar(
+    ctx: CanvasRenderingContext2D, xPos: number, bounds: BBox, label: string) {
+  if (xPos < bounds.x) return;
+
+  ctx.fillStyle = '#222';
+  ctx.fillRect(xPos, 0, 1, bounds.width);
+
+  const yMid = Math.floor(bounds.height / 2 + bounds.y);
+  const labelWidth = ctx.measureText(label).width;
+  const padding = 3;
+
+  let xPosLabel;
+  if (xPos + padding + labelWidth > bounds.width) {
+    xPosLabel = xPos - padding;
+    ctx.textAlign = 'right';
+  } else {
+    xPosLabel = xPos + padding;
+    ctx.textAlign = 'left';
+  }
+
+  ctx.fillStyle = '#ffffff';
+  ctx.fillRect(xPosLabel - 1, 0, labelWidth + 2, bounds.height);
+
+  ctx.textBaseline = 'middle';
+  ctx.fillStyle = '#222';
+  ctx.font = '10px Google Sans';
+  ctx.fillText(label, xPosLabel, yMid);
+}
+
 export class TimeSelectionPanel extends Panel {
   view() {
     return m('.time-selection-panel');
@@ -100,9 +129,20 @@
       const start = Math.min(selection.startTs, selection.endTs);
       const end = Math.max(selection.startTs, selection.endTs);
       this.renderSpan(ctx, size, new TimeSpan(start, end));
+    } else if (globals.frontendLocalState.showTimeSelectPreview) {
+      this.renderHover(ctx, size, globals.frontendLocalState.hoveredTimestamp);
     }
   }
 
+  renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: number) {
+    const timeScale = globals.frontendLocalState.timeScale;
+    const xPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(ts));
+    const offsetTime = timeToString(ts - globals.state.traceTime.startSec);
+    const timeFromStart = timeToString(ts);
+    const label = `${offsetTime} (${timeFromStart})`;
+    drawIBar(ctx, xPos, this.bounds(size), label);
+  }
+
   renderSpan(ctx: CanvasRenderingContext2D, size: PanelSize, span: TimeSpan) {
     const timeScale = globals.frontendLocalState.timeScale;
     const xLeft = timeScale.timeToPx(span.start);
@@ -116,7 +156,11 @@
           width: xRight - xLeft,
           height: size.height
         },
-        {x: 0, y: 0, width: size.width, height: size.height},
+        this.bounds(size),
         label);
   }
+
+  private bounds(size: PanelSize): BBox {
+    return {x: TRACK_SHELL_WIDTH, y: 0, width: size.width, height: size.height};
+  }
 }
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index 72fbf03..f8814ec 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -14,13 +14,12 @@
 
 import * as m from 'mithril';
 
-import {searchSegment} from '../base/binary_search';
 import {Actions} from '../common/actions';
 import {QueryResponse} from '../common/queries';
 import {EngineConfig} from '../common/state';
-import {fromNs, TimeSpan, toNs} from '../common/time';
 
 import {globals} from './globals';
+import {executeSearch} from './search_handler';
 
 const QUERY_ID = 'quicksearch';
 
@@ -37,7 +36,6 @@
 let numResults = 0;
 let mode: Mode = SEARCH;
 let displayStepThrough = false;
-let prevOmniBox: string|undefined = undefined;
 
 function clearOmniboxResults(e: Event) {
   globals.queryResults.delete(QUERY_ID);
@@ -50,8 +48,11 @@
 }
 
 function onKeyDown(e: Event) {
-  e.stopPropagation();
-  const key = (e as KeyboardEvent).key;
+  const event = (e as KeyboardEvent);
+  const key = event.key;
+  if (key !== 'Enter') {
+    e.stopPropagation();
+  }
   const txt = (e.target as HTMLInputElement);
 
   // Avoid that the global 'a', 'd', 'w', 's' handler sees these keystrokes.
@@ -73,12 +74,15 @@
     globals.rafScheduler.scheduleFullRedraw();
     return;
   }
+
+  if (mode === SEARCH && key === 'Enter') {
+    txt.blur();
+  }
 }
 
 function onKeyUp(e: Event) {
   e.stopPropagation();
   const event = (e as KeyboardEvent);
-  const state = globals.frontendLocalState;
   const key = event.key;
   const txt = e.target as HTMLInputElement;
   if (key === 'ArrowUp' || key === 'ArrowDown') {
@@ -103,86 +107,8 @@
     globals.dispatch(Actions.executeQuery(
         {engineId: '0', queryId: 'command', query: txt.value}));
   }
-  if (mode === SEARCH && key === 'Enter') {
-    const index = state.searchIndex;
-    const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-    const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
-    const currentTs = globals.currentSearchResults.tsStarts[index];
-    // If this is a new search or the currentTs is not in the viewport,
-    // select the first/last item in the viewport.
-    if (index === -1 || currentTs < startNs || currentTs > endNs) {
-      if (event.shiftKey) {
-        const [smaller,] =
-        searchSegment(globals.currentSearchResults.tsStarts, endNs);
-        globals.frontendLocalState.setSearchIndex(smaller);
-      } else {
-        const [, larger] =
-            searchSegment(globals.currentSearchResults.tsStarts, startNs);
-        globals.frontendLocalState.setSearchIndex(larger);
-      }
-      // If there is no result in the current viewport, move it.
-      const currentTs =
-          globals.currentSearchResults.tsStarts[state.searchIndex];
-      if (currentTs < startNs || currentTs > endNs) {
-        moveViewportToCurrent();
-      }
-    } else {
-      // If the currentTs is in the viewport, increment the index and move the
-      // viewport if necessary.
-      if (event.shiftKey) {
-        globals.frontendLocalState.setSearchIndex(Math.max(index - 1, 0));
-      } else {
-        globals.frontendLocalState.setSearchIndex(Math.min(
-            index + 1, globals.currentSearchResults.sliceIds.length - 1));
-      }
-      moveViewportToCurrent();
-    }
-    displaySearchResults();
-  }
 }
 
-function moveViewportToCurrent() {
-  // Move viewport if our selection moves outside.
-  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
-  const currentTs = globals.currentSearchResults
-                        .tsStarts[globals.frontendLocalState.searchIndex];
-  const currentViewNs = endNs - startNs;
-  if (currentTs < startNs || currentTs > endNs) {
-    // TODO(taylori): This is an ugly jump, we should do a smooth pan instead.
-    globals.frontendLocalState.updateVisibleTime(new TimeSpan(
-        fromNs(currentTs - currentViewNs / 2),
-        fromNs(currentTs + currentViewNs / 2)));
-  }
-}
-
-function displaySearchResults() {
-  const state = globals.frontendLocalState;
-  const current = globals.currentSearchResults;
-  if (current === undefined) return;
-  if (globals.frontendLocalState.omnibox !== prevOmniBox) {
-    globals.frontendLocalState.setSearchIndex(-1);
-  }
-  if (globals.frontendLocalState.omnibox === '' ||
-      globals.frontendLocalState.omnibox.length < 4) {
-    displayStepThrough = false;
-    return;
-  }
-  displayStepThrough = true;
-
-  const currentId = globals.currentSearchResults.sliceIds[state.searchIndex];
-  if (currentId !== undefined) {
-    globals.dispatch(Actions.selectSlice({
-      utid: globals.currentSearchResults.utids[state.searchIndex],
-      id: currentId
-    }));
-  }
-  // TODO(taylori): Here we should zoom to an appropriate level.
-  globals.rafScheduler.scheduleFullRedraw();
-  prevOmniBox = globals.frontendLocalState.omnibox;
-}
-
-
 class Omnibox implements m.ClassComponent {
   oncreate(vnode: m.VnodeDOM) {
     const txt = vnode.dom.querySelector('input') as HTMLInputElement;
@@ -192,7 +118,6 @@
   }
 
   view() {
-    displaySearchResults();
     const msgTTL = globals.state.status.timestamp + 1 - Date.now() / 1e3;
     let enginesAreBusy = false;
     for (const engine of Object.values(globals.state.engines)) {
@@ -228,9 +153,11 @@
           oninput: m.withAttr(
               'value',
               v => {
-                globals.frontendLocalState.omnibox = v;
-                if (v === '') {
-                  displayStepThrough = false;
+                globals.frontendLocalState.setOmnibox(
+                    v, commandMode ? 'COMMAND' : 'SEARCH');
+                if (mode === SEARCH) {
+                  globals.frontendLocalState.setSearchIndex(-1);
+                  displayStepThrough = v.length >= 4;
                   globals.rafScheduler.scheduleFullRedraw();
                 }
               }),
@@ -249,10 +176,7 @@
                   {
                     disabled: state.searchIndex <= 0,
                     onclick: () => {
-                      globals.frontendLocalState.setSearchIndex(
-                          state.searchIndex - 1);
-                      moveViewportToCurrent();
-                      displaySearchResults();
+                      executeSearch(true /* reverse direction */);
                     }
                   },
                   m('i.material-icons.left', 'keyboard_arrow_left')),
@@ -261,10 +185,7 @@
                     disabled: state.searchIndex ===
                         globals.currentSearchResults.totalResults - 1,
                     onclick: () => {
-                      globals.frontendLocalState.setSearchIndex(
-                          state.searchIndex + 1);
-                      moveViewportToCurrent();
-                      displaySearchResults();
+                      executeSearch();
                     }
                   },
                   m('i.material-icons.right', 'keyboard_arrow_right')),
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index acd475c..de688ff 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import * as m from 'mithril';
 import {TrackState} from '../common/state';
 import {TrackData} from '../common/track_data';
 
 import {globals} from './globals';
+import {TrackButtonAttrs} from './track_panel';
 
 /**
  * This interface forces track implementations to have some static properties.
@@ -51,6 +53,10 @@
     return 40;
   }
 
+  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
+    return [];
+  }
+
   onMouseMove(_position: {x: number, y: number}) {}
 
   /**
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 514fc4a..be93418 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -14,7 +14,7 @@
 
 import * as m from 'mithril';
 
-import {Actions, DeferredAction} from '../common/actions';
+import {Actions} from '../common/actions';
 import {TrackState} from '../common/state';
 
 import {globals} from './globals';
@@ -33,6 +33,7 @@
 }
 
 interface TrackShellAttrs {
+  track: Track;
   trackState: TrackState;
 }
 
@@ -64,9 +65,15 @@
             title: attrs.trackState.name,
           },
           attrs.trackState.name),
+        attrs.track.getTrackShellButtons(),
         m(TrackButton, {
-          action: Actions.toggleTrackPinned({trackId: attrs.trackState.id}),
+          action: () => {
+            globals.dispatch(
+                Actions.toggleTrackPinned({trackId: attrs.trackState.id}));
+          },
           i: isPinned(attrs.trackState.id) ? 'star' : 'star_border',
+          tooltip: isPinned(attrs.trackState.id) ? 'Unpin' : 'Pin to top',
+          selected: isPinned(attrs.trackState.id),
         }));
   }
 
@@ -167,22 +174,26 @@
           }
         },
         [
-          m(TrackShell, {trackState: attrs.trackState}),
+          m(TrackShell, {track: attrs.track, trackState: attrs.trackState}),
           m(TrackContent, {track: attrs.track})
         ]);
   }
 }
 
-interface TrackButtonAttrs {
-  action: DeferredAction;
+export interface TrackButtonAttrs {
+  action: () => void;
   i: string;
+  tooltip: string;
+  selected: boolean;
 }
-class TrackButton implements m.ClassComponent<TrackButtonAttrs> {
+export class TrackButton implements m.ClassComponent<TrackButtonAttrs> {
   view({attrs}: m.CVnode<TrackButtonAttrs>) {
     return m(
         'i.material-icons.track-button',
         {
-          onclick: () => globals.dispatch(attrs.action),
+          class: `${attrs.selected ? 'show' : ''}`,
+          onclick: attrs.action,
+          title: attrs.tooltip,
         },
         attrs.i);
   }
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 30ac1e5..a6c415f 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -21,8 +21,10 @@
 
 import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
 import {copyToClipboard} from './clipboard';
+import {CounterDetailsPanel} from './counter_panel';
 import {DragGestureHandler} from './drag_gesture_handler';
 import {globals} from './globals';
+import {HeapDumpDetailsPanel} from './heap_dump_panel';
 import {LogPanel} from './logs_panel';
 import {NotesEditorPanel, NotesPanel} from './notes_panel';
 import {OverviewTimelinePanel} from './overview_timeline_panel';
@@ -255,7 +257,7 @@
                                scale.pxToTime(startPx - TRACK_SHELL_WIDTH));
         const endTs = Math.min(traceTime.endSec,
                                scale.pxToTime(endPx - TRACK_SHELL_WIDTH));
-        globals.dispatch(Actions.selectTimeSpan({startTs, endTs}));
+        globals.makeSelection(Actions.selectTimeSpan({startTs, endTs}));
         globals.rafScheduler.scheduleRedraw();
       }
     });
@@ -301,6 +303,16 @@
             utid: curSelection.utid,
           }));
           break;
+        case 'COUNTER':
+          detailsPanels.push(m(CounterDetailsPanel, {
+            key: 'counter',
+          }));
+          break;
+        case 'HEAP_DUMP':
+          detailsPanels.push(m(HeapDumpDetailsPanel, {
+            key: 'heap_dump',
+          }));
+          break;
         case 'CHROME_SLICE':
           detailsPanels.push(m(ChromeSliceDetailsPanel));
           break;
@@ -340,7 +352,7 @@
                   this.keepCurrentSelection = false;
                   return;
                 }
-                globals.dispatch(Actions.deselect({}));
+                globals.makeSelection(Actions.deselect({}));
               }
             },
             m('.pinned-panel-container', m(PanelContainer, {
diff --git a/ui/src/tracks/all_controller.ts b/ui/src/tracks/all_controller.ts
index 7afaab6..79d3070 100644
--- a/ui/src/tracks/all_controller.ts
+++ b/ui/src/tracks/all_controller.ts
@@ -17,6 +17,7 @@
 import './android_log/controller';
 import './chrome_slices/controller';
 import './counter/controller';
+import './heap_profile/controller';
 import './cpu_freq/controller';
 import './gpu_freq/controller';
 import './cpu_slices/controller';
diff --git a/ui/src/tracks/all_frontend.ts b/ui/src/tracks/all_frontend.ts
index 9b59a76..dfa57fc 100644
--- a/ui/src/tracks/all_frontend.ts
+++ b/ui/src/tracks/all_frontend.ts
@@ -17,6 +17,7 @@
 import './android_log/frontend';
 import './chrome_slices/frontend';
 import './counter/frontend';
+import './heap_profile/frontend';
 import './cpu_freq/frontend';
 import './gpu_freq/frontend';
 import './cpu_slices/frontend';
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 80e67f6..7038b4a 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -159,7 +159,7 @@
     if (data === undefined) return false;
     const sliceId = data.slice_ids[sliceIndex];
     if (sliceId) {
-      globals.dispatch(Actions.selectChromeSlice({slice_id: sliceId}));
+      globals.makeSelection(Actions.selectChromeSlice({slice_id: sliceId}));
       return true;
     }
     return false;
diff --git a/ui/src/tracks/counter/common.ts b/ui/src/tracks/counter/common.ts
index ab2854a..0eb6dfb 100644
--- a/ui/src/tracks/counter/common.ts
+++ b/ui/src/tracks/counter/common.ts
@@ -23,6 +23,7 @@
 
   timestamps: Float64Array;
   values: Float64Array;
+  ids: Float64Array;
 }
 
 export interface Config {
@@ -30,4 +31,5 @@
   maximumValue?: number;
   minimumValue?: number;
   ref: number;
+  scale?: 'DEFAULT'|'RELATIVE';
 }
diff --git a/ui/src/tracks/counter/controller.ts b/ui/src/tracks/counter/controller.ts
index 2ef24af..4085845 100644
--- a/ui/src/tracks/counter/controller.ts
+++ b/ui/src/tracks/counter/controller.ts
@@ -94,15 +94,15 @@
       // Union that with the query that finds all the counters within
       // the current query range.
       query = `
-      select * from (select ts, value from counters
+      select * from (select ts, value, counter_id from counters
       where name = '${this.config.name}' and ref = ${this.config.ref} and
       ts <= ${startNs} order by ts desc limit 1)
       UNION
-      select * from (select ts, value
+      select * from (select ts, value, counter_id
         from (select
           ts,
           lead(ts, 1, ts) over (partition by ref_type order by ts) as ts_end,
-          value
+          value, counter_id
           from counters
           where name = '${this.config.name}' and ref = ${this.config.ref})
       where ts <= ${endNs} and ${startNs} <= ts_end limit ${LIMIT});`;
@@ -122,25 +122,36 @@
       resolution,
       timestamps: new Float64Array(numRows),
       values: new Float64Array(numRows),
+      ids: new Float64Array(numRows),
     };
 
     const cols = rawResult.columns;
     for (let row = 0; row < numRows; row++) {
       const startSec = fromNs(+cols[0].longValues![row]);
       const value = +cols[1].doubleValues![row];
+      const id = +cols[2].longValues![row];
       data.timestamps[row] = startSec;
       data.values[row] = value;
+      data.ids[row] = id;
     }
 
     return data;
   }
 
   private maximumValue() {
-    return Math.max(this.config.maximumValue || 0, this.maximumValueSeen);
+    if (this.config.maximumValue === undefined) {
+      return this.maximumValueSeen;
+    } else {
+      return this.config.maximumValue;
+    }
   }
 
   private minimumValue() {
-    return Math.min(this.config.minimumValue || 0, this.minimumValueSeen);
+    if (this.config.minimumValue === undefined) {
+      return this.minimumValueSeen;
+    } else {
+      return this.config.minimumValue;
+    }
   }
 
 }
diff --git a/ui/src/tracks/counter/frontend.ts b/ui/src/tracks/counter/frontend.ts
index 9226301..8060dd0 100644
--- a/ui/src/tracks/counter/frontend.ts
+++ b/ui/src/tracks/counter/frontend.ts
@@ -12,12 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import * as m from 'mithril';
+
 import {searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
+import {Actions} from '../../common/actions';
 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 {TrackButton, TrackButtonAttrs} from '../../frontend/track_panel';
 import {trackRegistry} from '../../frontend/track_registry';
 
 import {
@@ -45,6 +50,27 @@
     super(trackState);
   }
 
+  getTrackShellButtons(): Array<m.Vnode<TrackButtonAttrs>> {
+    const buttons: Array<m.Vnode<TrackButtonAttrs>> = [];
+    buttons.push(m(TrackButton, {
+      action: () => {
+        if (this.config.scale === 'RELATIVE') {
+          this.config.scale = 'DEFAULT';
+        } else {
+          this.config.scale = 'RELATIVE';
+        }
+        Actions.updateTrackConfig(
+            {id: this.trackState.id, config: this.config});
+        globals.rafScheduler.scheduleFullRedraw();
+      },
+      i: 'show_chart',
+      tooltip: (this.config.scale === 'RELATIVE') ? 'Use zero-based scale' :
+                                                    'Use relative scale',
+      selected: this.config.scale === 'RELATIVE',
+    }));
+    return buttons;
+  }
+
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
     const {timeScale, visibleWindowTime} = globals.frontendLocalState;
@@ -54,11 +80,10 @@
 
     assertTrue(data.timestamps.length === data.values.length);
 
-    const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start));
     const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end));
     const zeroY = MARGIN_TOP + RECT_HEIGHT / (data.minimumValue < 0 ? 2 : 1);
 
-    let lastX = startPx;
+    let lastX = Math.floor(timeScale.timeToPx(data.timestamps[0]));
     let lastY = zeroY;
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -69,9 +94,20 @@
     const exp = Math.ceil(Math.log10(Math.max(yMax, 1)));
     const pow10 = Math.pow(10, exp);
     yMax = Math.ceil(yMax / (pow10 / 4)) * (pow10 / 4);
-    const yRange = data.minimumValue < 0 ? yMax * 2 : yMax;
+    let yRange = 0;
     const unitGroup = Math.floor(exp / 3);
-    const yLabel = `${yMax / Math.pow(10, unitGroup * 3)} ${kUnits[unitGroup]}`;
+    let yMin = 0;
+    let yLabel = '';
+    if (this.config.scale === 'RELATIVE') {
+      yRange = data.maximumValue - data.minimumValue;
+      yMin = data.minimumValue;
+      yLabel = 'min - max';
+    } else {
+      yRange = data.minimumValue < 0 ? yMax * 2 : yMax;
+      yMin = data.minimumValue < 0 ? -yMax : 0;
+      yLabel = `${yMax / Math.pow(10, unitGroup * 3)} ${kUnits[unitGroup]}`;
+    }
+
     // There are 360deg of hue. We want a scale that starts at green with
     // exp <= 3 (<= 1KB), goes orange around exp = 6 (~1MB) and red/violet
     // around exp >= 9 (1GB).
@@ -90,7 +126,7 @@
     for (let i = 0; i < data.values.length; i++) {
       const value = data.values[i];
       const startTime = data.timestamps[i];
-      const nextY = zeroY - Math.round((value / yRange) * RECT_HEIGHT);
+      const nextY = zeroY - Math.round(((value - yMin) / yRange) * RECT_HEIGHT);
       if (nextY === lastY) continue;
 
       lastX = Math.floor(timeScale.timeToPx(startTime));
@@ -105,7 +141,7 @@
     ctx.stroke();
 
     // Draw the Y=0 dashed line.
-    ctx.strokeStyle = `hsl(${hue}, 10%, 15%)`;
+    ctx.strokeStyle = `hsl(${hue}, 10%, 71%)`;
     ctx.beginPath();
     ctx.setLineDash([2, 4]);
     ctx.moveTo(0, zeroY);
@@ -129,7 +165,8 @@
       const xEnd = this.hoveredTsEnd === undefined ?
           endPx :
           Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
-      const y = zeroY - Math.round((this.hoveredValue / yRange) * RECT_HEIGHT);
+      const y = zeroY -
+          Math.round(((this.hoveredValue - yMin) / yRange) * RECT_HEIGHT);
 
       // Highlight line.
       ctx.beginPath();
@@ -184,18 +221,31 @@
     this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
     this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
     this.hoveredValue = left === -1 ? undefined : data.values[left];
-
-    // for (let i = 0; i < data.values.length; i++) {
-    //  if (data.timestamps[i] > time) break;
-    //  this.hoveredTs = data.timestamps[i];
-    //  this.hoveredValue = data.values[i];
-    //}
   }
 
   onMouseOut() {
     this.hoveredValue = undefined;
     this.hoveredTs = undefined;
   }
+
+  onMouseClick({x}: {x: number}) {
+    const data = this.data();
+    if (data === undefined) return false;
+    const {timeScale} = globals.frontendLocalState;
+    const time = timeScale.pxToTime(x);
+    const [left, right] = searchSegment(data.timestamps, time);
+    if (left === -1) {
+      return false;
+    } else {
+      const counterId = data.ids[left];
+      globals.makeSelection(Actions.selectCounter({
+        leftTs: toNs(data.timestamps[left]),
+        rightTs: right !== -1 ? toNs(data.timestamps[right]) : -1,
+        id: counterId
+      }));
+      return true;
+    }
+  }
 }
 
 trackRegistry.register(CounterTrack);
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index c158672..a25f7ba 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -307,9 +307,8 @@
     const index = search(data.starts, time);
     const id = index === -1 ? undefined : data.ids[index];
     if (id && this.utidHoveredInThisTrack !== -1) {
-      globals.frontendLocalState.searchIndex = -1;
-      globals.dispatch(Actions.selectSlice(
-        {utid: this.utidHoveredInThisTrack, id}));
+      globals.makeSelection(
+          Actions.selectSlice({utid: this.utidHoveredInThisTrack, id}));
       return true;
     }
     return false;
diff --git a/ui/src/tracks/heap_profile/common.ts b/ui/src/tracks/heap_profile/common.ts
new file mode 100644
index 0000000..fc9da62
--- /dev/null
+++ b/ui/src/tracks/heap_profile/common.ts
@@ -0,0 +1,24 @@
+// 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 {TrackData} from '../../common/track_data';
+
+export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
+
+export interface Data extends TrackData {
+  tsStarts: Float64Array;
+}
+
+export interface Config {
+  upid: number;
+}
diff --git a/ui/src/tracks/heap_profile/controller.ts b/ui/src/tracks/heap_profile/controller.ts
new file mode 100644
index 0000000..61ad335
--- /dev/null
+++ b/ui/src/tracks/heap_profile/controller.ts
@@ -0,0 +1,53 @@
+// 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 {
+  TrackController,
+  trackControllerRegistry
+} from '../../controller/track_controller';
+
+import {
+  Config,
+  Data,
+  HEAP_PROFILE_TRACK_KIND,
+} from './common';
+
+class HeapProfileTrackController extends TrackController<Config, Data> {
+  static readonly kind = HEAP_PROFILE_TRACK_KIND;
+  async onBoundsChange(start: number, end: number, resolution: number):
+      Promise<Data> {
+    if (this.config.upid === undefined) {
+      return {start, end, resolution, length: 0, tsStarts: new Float64Array()};
+    }
+    const result = await this.query(`
+    select distinct(ts) from heap_profile_allocation where upid = ${
+        this.config.upid}`);
+    const numRows = +result.numRecords;
+    const data: Data = {
+      start,
+      end,
+      resolution,
+      length: numRows,
+      tsStarts: new Float64Array(numRows),
+    };
+
+    for (let row = 0; row < numRows; row++) {
+      data.tsStarts[row] = +result.columns[0].longValues![row];
+    }
+
+    return data;
+  }
+}
+
+trackControllerRegistry.register(HeapProfileTrackController);
diff --git a/ui/src/tracks/heap_profile/frontend.ts b/ui/src/tracks/heap_profile/frontend.ts
new file mode 100644
index 0000000..20a850e
--- /dev/null
+++ b/ui/src/tracks/heap_profile/frontend.ts
@@ -0,0 +1,110 @@
+// 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 {searchSegment} from '../../base/binary_search';
+import {Actions} from '../../common/actions';
+import {TrackState} from '../../common/state';
+import {fromNs, toNs} from '../../common/time';
+import {globals} from '../../frontend/globals';
+import {Track} from '../../frontend/track';
+import {trackRegistry} from '../../frontend/track_registry';
+
+import {Config, Data, HEAP_PROFILE_TRACK_KIND} from './common';
+
+// 0.5 Makes the horizontal lines sharp.
+const MARGIN_TOP = 4.5;
+const RECT_HEIGHT = 30.5;
+
+class HeapProfileTrack extends Track<Config, Data> {
+  private centerY = this.getHeight() / 2;
+  private width = (this.getHeight() - MARGIN_TOP) / 2;
+
+  static readonly kind = HEAP_PROFILE_TRACK_KIND;
+  static create(trackState: TrackState): HeapProfileTrack {
+    return new HeapProfileTrack(trackState);
+  }
+
+  constructor(trackState: TrackState) {
+    super(trackState);
+  }
+
+  getHeight() {
+    return MARGIN_TOP + RECT_HEIGHT - 1;
+  }
+
+  renderCanvas(ctx: CanvasRenderingContext2D): void {
+    const {
+      timeScale,
+    } = globals.frontendLocalState;
+    const data = this.data();
+
+    if (data === undefined) return;
+
+    for (let i = 0; i < data.tsStarts.length; i++) {
+      const centerX = data.tsStarts[i];
+      this.drawMarker(ctx, timeScale.timeToPx(fromNs(centerX)), this.centerY);
+    }
+  }
+
+  drawMarker(ctx: CanvasRenderingContext2D, x: number, y: number): void {
+    ctx.fillStyle = '#d9b3ff';
+    ctx.beginPath();
+    ctx.moveTo(x, y - this.width);
+    ctx.lineTo(x - this.width, y);
+    ctx.lineTo(x, y + this.width);
+    ctx.lineTo(x + this.width, y);
+    ctx.lineTo(x, y - this.width);
+    ctx.fill();
+    ctx.closePath();
+  }
+
+  // TODO(tneda): Add a border to show the currently selected marker and
+  // a hover state.
+  onMouseClick({x, y}: {x: number, y: number}) {
+    const data = this.data();
+    if (data === undefined) return false;
+    const {timeScale} = globals.frontendLocalState;
+
+    const time = toNs(timeScale.pxToTime(x));
+    const [left, right] = searchSegment(data.tsStarts, time);
+
+    let index = -1;
+    if (left !== -1) {
+      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[left]));
+      if (this.isInMarker(x, y, centerX)) {
+        index = left;
+      }
+    }
+    if (right !== -1) {
+      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[right]));
+      if (this.isInMarker(x, y, centerX)) {
+        index = right;
+      }
+    }
+
+    // If the markers overlap the rightmost one will be selected.
+    if (index !== -1) {
+      globals.makeSelection(Actions.selectHeapDump(
+          {id: index, upid: this.config.upid, ts: data.tsStarts[index]}));
+      return true;
+    }
+    return false;
+  }
+
+  isInMarker(x: number, y: number, centerX: number) {
+    return Math.abs(x - centerX) + Math.abs(y - this.centerY) <= this.width;
+  }
+}
+
+trackRegistry.register(HeapProfileTrack);
diff --git a/ui/src/tracks/process_summary/controller.ts b/ui/src/tracks/process_summary/controller.ts
index d18cb6b..a7a73aa 100644
--- a/ui/src/tracks/process_summary/controller.ts
+++ b/ui/src/tracks/process_summary/controller.ts
@@ -83,7 +83,8 @@
       bucketSizeNs: number): Promise<Data> {
     const startNs = toNs(start);
     const endNs = toNs(end);
-    const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs);
+    const numBuckets =
+        Math.min(Math.ceil((endNs - startNs) / bucketSizeNs), LIMIT);
 
     const query = `select
       quantum_ts as bucket,
@@ -99,13 +100,16 @@
       start,
       end,
       resolution,
-      length: numRows,
+      length: numBuckets,
       bucketSizeSeconds: fromNs(bucketSizeNs),
       utilizations: new Float64Array(numBuckets),
     };
     const cols = rawResult.columns;
     for (let row = 0; row < numRows; row++) {
       const bucket = +cols[0].longValues![row];
+      if (bucket > numBuckets) {
+        continue;
+      }
       summary.utilizations[bucket] = +cols[1].doubleValues![row];
     }
     return summary;
diff --git a/ui/src/tracks/thread_state/frontend.ts b/ui/src/tracks/thread_state/frontend.ts
index 85c4504..d4fc287 100644
--- a/ui/src/tracks/thread_state/frontend.ts
+++ b/ui/src/tracks/thread_state/frontend.ts
@@ -118,7 +118,7 @@
     const cpu = index === -1 ? undefined : data.cpu[index];
     const utid = this.config.utid;
     if (ts && state && tsEnd && cpu !== undefined) {
-      globals.dispatch(
+      globals.makeSelection(
           Actions.selectThreadState({utid, ts, dur: tsEnd - ts, state, cpu}));
       return true;
     }