Merge "trace_processor: associate rss stat and mem info events with process"
diff --git a/Android.bp b/Android.bp
index 51dfcc0..4b67947 100644
--- a/Android.bp
+++ b/Android.bp
@@ -52,6 +52,7 @@
     "src/ipc/service_proxy.cc",
     "src/ipc/virtual_destructors.cc",
     "src/profiling/memory/bookkeeping.cc",
+    "src/profiling/memory/heapprofd_producer.cc",
     "src/profiling/memory/main.cc",
     "src/profiling/memory/record_reader.cc",
     "src/profiling/memory/socket_listener.cc",
@@ -62,6 +63,7 @@
     "src/protozero/message_handle.cc",
     "src/protozero/proto_decoder.cc",
     "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/tracing/core/chrome_config.cc",
@@ -206,6 +208,7 @@
     "src/protozero/message_handle.cc",
     "src/protozero/proto_decoder.cc",
     "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/traced/probes/filesystem/file_scanner.cc",
@@ -331,12 +334,14 @@
     "src/ipc/service_proxy.cc",
     "src/ipc/virtual_destructors.cc",
     "src/perfetto_cmd/main.cc",
+    "src/perfetto_cmd/pbtxt_to_pb.cc",
     "src/perfetto_cmd/perfetto_cmd.cc",
     "src/perfetto_cmd/rate_limiter.cc",
     "src/protozero/message.cc",
     "src/protozero/message_handle.cc",
     "src/protozero/proto_decoder.cc",
     "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/tracing/core/chrome_config.cc",
@@ -479,7 +484,7 @@
     "src/protozero/message_handle.cc",
     "src/protozero/proto_decoder.cc",
     "src/protozero/proto_field_descriptor.cc",
-    "src/protozero/scattered_stream_delegate_for_testing.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/traced/probes/filesystem/file_scanner.cc",
@@ -3928,6 +3933,7 @@
 genrule {
   name: "perfetto_src_perfetto_cmd_protos_gen",
   srcs: [
+    "src/perfetto_cmd/descriptor.proto",
     "src/perfetto_cmd/perfetto_cmd_state.proto",
   ],
   tools: [
@@ -3935,6 +3941,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto/ $(in)",
   out: [
+    "external/perfetto/src/perfetto_cmd/descriptor.pb.cc",
     "external/perfetto/src/perfetto_cmd/perfetto_cmd_state.pb.cc",
   ],
 }
@@ -3943,6 +3950,7 @@
 genrule {
   name: "perfetto_src_perfetto_cmd_protos_gen_headers",
   srcs: [
+    "src/perfetto_cmd/descriptor.proto",
     "src/perfetto_cmd/perfetto_cmd_state.proto",
   ],
   tools: [
@@ -3950,6 +3958,7 @@
   ],
   cmd: "mkdir -p $(genDir)/external/perfetto && $(location aprotoc) --cpp_out=$(genDir)/external/perfetto --proto_path=external/perfetto/ $(in)",
   out: [
+    "external/perfetto/src/perfetto_cmd/descriptor.pb.h",
     "external/perfetto/src/perfetto_cmd/perfetto_cmd_state.pb.h",
   ],
   export_include_dirs: [
@@ -4177,6 +4186,7 @@
     "src/protozero/message_handle.cc",
     "src/protozero/proto_decoder.cc",
     "src/protozero/proto_field_descriptor.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/tracing/core/chrome_config.cc",
@@ -4384,6 +4394,8 @@
     "src/ipc/service_proxy.cc",
     "src/ipc/test/ipc_integrationtest.cc",
     "src/ipc/virtual_destructors.cc",
+    "src/perfetto_cmd/pbtxt_to_pb.cc",
+    "src/perfetto_cmd/pbtxt_to_pb_unittest.cc",
     "src/perfetto_cmd/perfetto_cmd.cc",
     "src/perfetto_cmd/rate_limiter.cc",
     "src/perfetto_cmd/rate_limiter_unittest.cc",
@@ -4393,6 +4405,7 @@
     "src/profiling/memory/client.cc",
     "src/profiling/memory/client_unittest.cc",
     "src/profiling/memory/heapprofd_integrationtest.cc",
+    "src/profiling/memory/heapprofd_producer.cc",
     "src/profiling/memory/record_reader.cc",
     "src/profiling/memory/record_reader_unittest.cc",
     "src/profiling/memory/sampler.cc",
@@ -4413,7 +4426,7 @@
     "src/protozero/proto_decoder_unittest.cc",
     "src/protozero/proto_field_descriptor.cc",
     "src/protozero/proto_utils_unittest.cc",
-    "src/protozero/scattered_stream_delegate_for_testing.cc",
+    "src/protozero/scattered_stream_memory_delegate.cc",
     "src/protozero/scattered_stream_null_delegate.cc",
     "src/protozero/scattered_stream_writer.cc",
     "src/protozero/scattered_stream_writer_unittest.cc",
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2d54a34..9a4902f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -81,6 +81,21 @@
     return []
 
 
+def CheckBinaryDescriptors(input_api, output_api):
+    tool = 'tools/gen_binary_descriptors'
+    file_filter = lambda x: input_api.FilterSourceFile(
+          x,
+          white_list=('.*[.]h$', '.*[.]proto$', tool))
+    if not input_api.AffectedSourceFiles(file_filter):
+        return []
+    if subprocess.call([tool, '--check-only']):
+        return [
+            output_api.PresubmitError(
+                'Please run ' + tool + ' to update binary descriptors.')
+        ]
+    return []
+
+
 def CheckMergedTraceConfigProto(input_api, output_api):
     tool = 'tools/gen_merged_protos'
     build_file_filter = lambda x: input_api.FilterSourceFile(
@@ -102,7 +117,7 @@
   for f in input_api.AffectedFiles():
     if f.LocalPath() != 'tools/ftrace_proto_gen/event_whitelist':
       continue
-    if any((not new_line.startswith('removed')) 
+    if any((not new_line.startswith('removed'))
             and new_line != old_line for old_line, new_line
            in itertools.izip(f.OldContents(), f.NewContents())):
       return [
diff --git a/heapprofd.rc b/heapprofd.rc
index 4791103..10bc845 100644
--- a/heapprofd.rc
+++ b/heapprofd.rc
@@ -18,3 +18,4 @@
     user nobody
     group nobody
     writepid /dev/cpuset/system-background/tasks
+    capabilities KILL
diff --git a/include/perfetto/protozero/BUILD.gn b/include/perfetto/protozero/BUILD.gn
index cc75d2d8..06b067f 100644
--- a/include/perfetto/protozero/BUILD.gn
+++ b/include/perfetto/protozero/BUILD.gn
@@ -23,6 +23,7 @@
     "proto_decoder.h",
     "proto_field_descriptor.h",
     "proto_utils.h",
+    "scattered_stream_memory_delegate.h",
     "scattered_stream_null_delegate.h",
     "scattered_stream_writer.h",
   ]
diff --git a/src/protozero/scattered_stream_delegate_for_testing.h b/include/perfetto/protozero/scattered_stream_memory_delegate.h
similarity index 79%
rename from src/protozero/scattered_stream_delegate_for_testing.h
rename to include/perfetto/protozero/scattered_stream_memory_delegate.h
index 365ad43..5910fbd 100644
--- a/src/protozero/scattered_stream_delegate_for_testing.h
+++ b/include/perfetto/protozero/scattered_stream_memory_delegate.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_PROTOZERO_SCATTERED_STREAM_DELEGATE_FOR_TESTING_H_
-#define SRC_PROTOZERO_SCATTERED_STREAM_DELEGATE_FOR_TESTING_H_
+#ifndef INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_MEMORY_DELEGATE_H_
+#define INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_MEMORY_DELEGATE_H_
 
 #include <memory>
 #include <vector>
@@ -25,11 +25,11 @@
 
 namespace perfetto {
 
-class ScatteredStreamDelegateForTesting
+class ScatteredStreamMemoryDelegate
     : public protozero::ScatteredStreamWriter::Delegate {
  public:
-  explicit ScatteredStreamDelegateForTesting(size_t chunk_size);
-  ~ScatteredStreamDelegateForTesting() override;
+  explicit ScatteredStreamMemoryDelegate(size_t chunk_size);
+  ~ScatteredStreamMemoryDelegate() override;
 
   // protozero::ScatteredStreamWriter::Delegate implementation.
   protozero::ContiguousMemoryRange GetNewBuffer() override;
@@ -54,4 +54,4 @@
 
 }  // namespace perfetto
 
-#endif  // SRC_PROTOZERO_SCATTERED_STREAM_DELEGATE_FOR_TESTING_H_
+#endif  // INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_MEMORY_DELEGATE_H_
diff --git a/protos/perfetto/config/BUILD.gn b/protos/perfetto/config/BUILD.gn
index dd8fb4e..75b496b 100644
--- a/protos/perfetto/config/BUILD.gn
+++ b/protos/perfetto/config/BUILD.gn
@@ -20,6 +20,10 @@
   generate_python = false
   proto_in_dir = "$perfetto_root_path/protos"
   proto_out_dir = "$perfetto_root_path/protos"
+  if (!build_with_chromium) {
+    generate_descriptor =
+        "$perfetto_root_path/protos/perfetto/trace/config.descriptor"
+  }
   deps = [
     "../common:lite",
   ]
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index fa9a2ed..381539e 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -299,6 +299,8 @@
 
   // If > 0 samples counters (see process_stats.proto) from
   // /proc/pid/status every X ms.
+  // This is required to be > 100ms to avoid excessive CPU usage.
+  // TODO: add CPU cost for change this value.
   optional uint32 proc_stats_poll_ms = 4;
 
   // If empty samples stats for all processes. If non empty samples stats only
@@ -327,16 +329,22 @@
 // Not OK: [10ms, 10ms, 11ms],  [10ms, 15ms, 20ms]
 message SysStatsConfig {
   // Polls /proc/meminfo every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 0.3 ms [read] + 0.07 ms [parse + trace injection]
   optional uint32 meminfo_period_ms = 1;
 
   // Only the counters specified below are reported.
   repeated MeminfoCounters meminfo_counters = 2;
 
   // Polls /proc/vmstat every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 0.2 ms [read] + 0.3 ms [parse + trace injection]
   optional uint32 vmstat_period_ms = 3;
   repeated VmstatCounters vmstat_counters = 4;
 
   // Pols /proc/stat every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 4.1 ms [read] + 1.9 ms [parse + trace injection]
   optional uint32 stat_period_ms = 5;
   enum StatCounters {
     STAT_UNSPECIFIED = 0;
@@ -357,6 +365,23 @@
 
 // The configuration for a fake producer used in tests.
 message TestConfig {
+  message DummyFields {
+    optional uint32 field_uint32 = 1;
+    optional int32 field_int32 = 2;
+    optional uint64 field_uint64 = 3;
+    optional int64 field_int64 = 4;
+    optional fixed64 field_fixed64 = 5;
+    optional sfixed64 field_sfixed64 = 6;
+    optional fixed32 field_fixed32 = 7;
+    optional sfixed32 field_sfixed32 = 8;
+    optional double field_double = 9;
+    optional float field_float = 10;
+    optional sint64 field_sint64 = 11;
+    optional sint32 field_sint32 = 12;
+    optional string field_string = 13;
+    optional bytes field_bytes = 14;
+  }
+
   // The number of messages the fake producer should send.
   optional uint32 message_count = 1;
 
@@ -378,6 +403,8 @@
   // Whether the producer should send a event batch when the data source is
   // is initially registered.
   optional bool send_batch_on_register = 5;
+
+  optional DummyFields dummy_fields = 6;
 }
 
 // End of protos/perfetto/config/test_config.proto
diff --git a/protos/perfetto/config/process_stats/process_stats_config.proto b/protos/perfetto/config/process_stats/process_stats_config.proto
index d1aa8b7..3ac7984 100644
--- a/protos/perfetto/config/process_stats/process_stats_config.proto
+++ b/protos/perfetto/config/process_stats/process_stats_config.proto
@@ -45,6 +45,8 @@
 
   // If > 0 samples counters (see process_stats.proto) from
   // /proc/pid/status every X ms.
+  // This is required to be > 100ms to avoid excessive CPU usage.
+  // TODO(primiano): add CPU cost for change this value.
   optional uint32 proc_stats_poll_ms = 4;
 
   // If empty samples stats for all processes. If non empty samples stats only
diff --git a/protos/perfetto/config/sys_stats/sys_stats_config.proto b/protos/perfetto/config/sys_stats/sys_stats_config.proto
index ef958b3..a773c3e 100644
--- a/protos/perfetto/config/sys_stats/sys_stats_config.proto
+++ b/protos/perfetto/config/sys_stats/sys_stats_config.proto
@@ -35,16 +35,22 @@
 // Not OK: [10ms, 10ms, 11ms],  [10ms, 15ms, 20ms]
 message SysStatsConfig {
   // Polls /proc/meminfo every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 0.3 ms [read] + 0.07 ms [parse + trace injection]
   optional uint32 meminfo_period_ms = 1;
 
   // Only the counters specified below are reported.
   repeated MeminfoCounters meminfo_counters = 2;
 
   // Polls /proc/vmstat every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 0.2 ms [read] + 0.3 ms [parse + trace injection]
   optional uint32 vmstat_period_ms = 3;
   repeated VmstatCounters vmstat_counters = 4;
 
   // Pols /proc/stat every X ms, if non-zero.
+  // This is required to be > 10ms to avoid excessive CPU usage.
+  // Cost: 4.1 ms [read] + 1.9 ms [parse + trace injection]
   optional uint32 stat_period_ms = 5;
   enum StatCounters {
     STAT_UNSPECIFIED = 0;
diff --git a/protos/perfetto/config/test_config.proto b/protos/perfetto/config/test_config.proto
index c8d57d5..b50012e 100644
--- a/protos/perfetto/config/test_config.proto
+++ b/protos/perfetto/config/test_config.proto
@@ -24,6 +24,23 @@
 
 // The configuration for a fake producer used in tests.
 message TestConfig {
+  message DummyFields {
+    optional uint32 field_uint32 = 1;
+    optional int32 field_int32 = 2;
+    optional uint64 field_uint64 = 3;
+    optional int64 field_int64 = 4;
+    optional fixed64 field_fixed64 = 5;
+    optional sfixed64 field_sfixed64 = 6;
+    optional fixed32 field_fixed32 = 7;
+    optional sfixed32 field_sfixed32 = 8;
+    optional double field_double = 9;
+    optional float field_float = 10;
+    optional sint64 field_sint64 = 11;
+    optional sint32 field_sint32 = 12;
+    optional string field_string = 13;
+    optional bytes field_bytes = 14;
+  }
+
   // The number of messages the fake producer should send.
   optional uint32 message_count = 1;
 
@@ -45,4 +62,6 @@
   // Whether the producer should send a event batch when the data source is
   // is initially registered.
   optional bool send_batch_on_register = 5;
+
+  optional DummyFields dummy_fields = 6;
 }
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index bc7af38..860eb48 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -321,6 +321,7 @@
   // TODO: Figure out a story for reconciling the various clocks.
   optional uint64 timestamp = 1;
 
+  // Kernel pid (do not confuse with userspace pid aka tgid)
   optional uint32 pid = 2;
 
   oneof event {
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 520185f..8f00dcf 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -233,6 +233,7 @@
   // TODO: Figure out a story for reconciling the various clocks.
   optional uint64 timestamp = 1;
 
+  // Kernel pid (do not confuse with userspace pid aka tgid)
   optional uint32 pid = 2;
 
   oneof event {
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index d71b1ed..175d2b0 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -21,6 +21,7 @@
     "../../include/perfetto/traced",
   ]
   deps = [
+    "../../buildtools:protobuf_lite",
     "../../gn:default_deps",
     "../../protos/perfetto/config:lite",
     "../base",
@@ -28,8 +29,11 @@
     "../tracing:ipc_consumer",
   ]
   sources = [
+    "pbtxt_to_pb.cc",
+    "pbtxt_to_pb.h",
     "perfetto_cmd.cc",
     "perfetto_cmd.h",
+    "perfetto_config.descriptor.h",
     "rate_limiter.cc",
     "rate_limiter.h",
   ]
@@ -39,6 +43,7 @@
   generate_python = false
   deps = []
   sources = [
+    "descriptor.proto",
     "perfetto_cmd_state.proto",
   ]
   proto_in_dir = perfetto_root_path
@@ -53,8 +58,10 @@
     "../../gn:default_deps",
     "../../gn:gtest_deps",
     "../../include/perfetto/base",
+    "../../protos/perfetto/config:lite",
   ]
   sources = [
+    "pbtxt_to_pb_unittest.cc",
     "rate_limiter_unittest.cc",
   ]
 }
diff --git a/src/perfetto_cmd/descriptor.proto b/src/perfetto_cmd/descriptor.proto
new file mode 100644
index 0000000..be0fff5
--- /dev/null
+++ b/src/perfetto_cmd/descriptor.proto
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+// This is a subset of descriptor.proto from the Protobuf library.
+syntax = "proto2";
+
+package perfetto.protos;
+
+option optimize_for = LITE_RUNTIME;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+  repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+  optional string name = 1;     // file name, relative to root of source tree
+  optional string package = 2;  // e.g. "foo", "foo.bar", etc.
+
+  // Names of files imported by this file.
+  repeated string dependency = 3;
+  // Indexes of the public imported files in the dependency list above.
+  repeated int32 public_dependency = 10;
+  // Indexes of the weak imported files in the dependency list.
+  // For Google-internal migration only. Do not use.
+  repeated int32 weak_dependency = 11;
+
+  // All top-level definitions in this file.
+  repeated DescriptorProto message_type = 4;
+  repeated EnumDescriptorProto enum_type = 5;
+
+  reserved 6;
+  reserved 7;
+  reserved 8;
+  reserved 9;
+  reserved 12;
+}
+
+// Describes a message type.
+message DescriptorProto {
+  optional string name = 1;
+
+  repeated FieldDescriptorProto field = 2;
+  repeated FieldDescriptorProto extension = 6;
+
+  repeated DescriptorProto nested_type = 3;
+  repeated EnumDescriptorProto enum_type = 4;
+
+  reserved 5;
+
+  repeated OneofDescriptorProto oneof_decl = 8;
+
+  reserved 7;
+
+  // Range of reserved tag numbers. Reserved tag numbers may not be used by
+  // fields or extension ranges in the same message. Reserved ranges may
+  // not overlap.
+  message ReservedRange {
+    optional int32 start = 1;  // Inclusive.
+    optional int32 end = 2;    // Exclusive.
+  }
+  repeated ReservedRange reserved_range = 9;
+  // Reserved field names, which may not be used by fields in the same message.
+  // A given name may only be reserved once.
+  repeated string reserved_name = 10;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+  enum Type {
+    // 0 is reserved for errors.
+    // Order is weird for historical reasons.
+    TYPE_DOUBLE = 1;
+    TYPE_FLOAT = 2;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
+    // negative values are likely.
+    TYPE_INT64 = 3;
+    TYPE_UINT64 = 4;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
+    // negative values are likely.
+    TYPE_INT32 = 5;
+    TYPE_FIXED64 = 6;
+    TYPE_FIXED32 = 7;
+    TYPE_BOOL = 8;
+    TYPE_STRING = 9;
+    // Tag-delimited aggregate.
+    // Group type is deprecated and not supported in proto3. However, Proto3
+    // implementations should still be able to parse the group wire format and
+    // treat group fields as unknown fields.
+    TYPE_GROUP = 10;
+    TYPE_MESSAGE = 11;  // Length-delimited aggregate.
+
+    // New in version 2.
+    TYPE_BYTES = 12;
+    TYPE_UINT32 = 13;
+    TYPE_ENUM = 14;
+    TYPE_SFIXED32 = 15;
+    TYPE_SFIXED64 = 16;
+    TYPE_SINT32 = 17;  // Uses ZigZag encoding.
+    TYPE_SINT64 = 18;  // Uses ZigZag encoding.
+  };
+
+  enum Label {
+    // 0 is reserved for errors
+    LABEL_OPTIONAL = 1;
+    LABEL_REQUIRED = 2;
+    LABEL_REPEATED = 3;
+  };
+
+  optional string name = 1;
+  optional int32 number = 3;
+  optional Label label = 4;
+
+  // If type_name is set, this need not be set.  If both this and type_name
+  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+  optional Type type = 5;
+
+  // For message and enum types, this is the name of the type.  If the name
+  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
+  // rules are used to find the type (i.e. first the nested types within this
+  // message are searched, then within the parent, on up to the root
+  // namespace).
+  optional string type_name = 6;
+
+  reserved 2;
+
+  // For numeric types, contains the original text representation of the value.
+  // For booleans, "true" or "false".
+  // For strings, contains the default text contents (not escaped in any way).
+  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
+  // TODO(kenton):  Base-64 encode?
+  optional string default_value = 7;
+
+  // If set, gives the index of a oneof in the containing type's oneof_decl
+  // list.  This field is a member of that oneof.
+  optional int32 oneof_index = 9;
+
+  reserved 10;
+
+  reserved 8;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+  optional string name = 1;
+  optional OneofOptions options = 2;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+  optional string name = 1;
+
+  repeated EnumValueDescriptorProto value = 2;
+
+  reserved 3;
+  reserved 4;
+
+  // Reserved enum value names, which may not be reused. A given name may only
+  // be reserved once.
+  repeated string reserved_name = 5;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+  optional string name = 1;
+  optional int32 number = 2;
+
+  reserved 3;
+}
+
+message OneofOptions {
+  reserved 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
diff --git a/src/perfetto_cmd/pbtxt_to_pb.cc b/src/perfetto_cmd/pbtxt_to_pb.cc
new file mode 100644
index 0000000..ec4af7d
--- /dev/null
+++ b/src/perfetto_cmd/pbtxt_to_pb.cc
@@ -0,0 +1,615 @@
+/*
+ * 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 <ctype.h>
+#include <set>
+#include <stack>
+#include <string>
+
+#include "src/perfetto_cmd/pbtxt_to_pb.h"
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "src/perfetto_cmd/descriptor.pb.h"
+
+#include "perfetto/base/file_utils.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/string_view.h"
+#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/scattered_stream_memory_delegate.h"
+#include "src/perfetto_cmd/perfetto_config.descriptor.h"
+
+namespace perfetto {
+constexpr char kConfigProtoName[] = ".perfetto.protos.TraceConfig";
+
+using protos::DescriptorProto;
+using protos::EnumDescriptorProto;
+using protos::EnumValueDescriptorProto;
+using protos::FieldDescriptorProto;
+using protos::FileDescriptorSet;
+using ::google::protobuf::io::ZeroCopyInputStream;
+using ::google::protobuf::io::ArrayInputStream;
+
+namespace {
+
+constexpr bool IsIdentifierStart(char c) {
+  return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || c == '_';
+}
+
+constexpr bool IsIdentifierBody(char c) {
+  return IsIdentifierStart(c) || isdigit(c);
+}
+
+const char* FieldToTypeName(const FieldDescriptorProto* field) {
+  switch (field->type()) {
+    case FieldDescriptorProto::TYPE_UINT64:
+      return "uint64";
+    case FieldDescriptorProto::TYPE_UINT32:
+      return "uint32";
+    case FieldDescriptorProto::TYPE_INT64:
+      return "int64";
+    case FieldDescriptorProto::TYPE_SINT64:
+      return "sint64";
+    case FieldDescriptorProto::TYPE_INT32:
+      return "int32";
+    case FieldDescriptorProto::TYPE_SINT32:
+      return "sint32";
+    case FieldDescriptorProto::TYPE_FIXED64:
+      return "fixed64";
+    case FieldDescriptorProto::TYPE_SFIXED64:
+      return "sfixed64";
+    case FieldDescriptorProto::TYPE_FIXED32:
+      return "fixed32";
+    case FieldDescriptorProto::TYPE_SFIXED32:
+      return "sfixed32";
+    case FieldDescriptorProto::TYPE_DOUBLE:
+      return "double";
+    case FieldDescriptorProto::TYPE_FLOAT:
+      return "float";
+    case FieldDescriptorProto::TYPE_BOOL:
+      return "bool";
+    case FieldDescriptorProto::TYPE_STRING:
+      return "string";
+    case FieldDescriptorProto::TYPE_BYTES:
+      return "bytes";
+    case FieldDescriptorProto::TYPE_GROUP:
+      return "group";
+    case FieldDescriptorProto::TYPE_MESSAGE:
+      return "message";
+    case FieldDescriptorProto::TYPE_ENUM:
+      return "enum";
+  }
+  // For gcc
+  PERFETTO_FATAL("Non conmplete switch");
+}
+
+std::string Format(const char* fmt, std::map<std::string, std::string> args) {
+  std::string result(fmt);
+  for (const auto& key_value : args) {
+    size_t start = result.find(key_value.first);
+    PERFETTO_CHECK(start != std::string::npos);
+    result.replace(start, key_value.first.size(), key_value.second);
+    PERFETTO_CHECK(result.find(key_value.first) == std::string::npos);
+  }
+  return result;
+}
+
+enum ParseState {
+  kWaitingForKey,
+  kReadingKey,
+  kWaitingForValue,
+  kReadingStringValue,
+  kReadingNumericValue,
+  kReadingIdentifierValue,
+};
+
+struct Token {
+  size_t offset;
+  size_t column;
+  size_t row;
+  base::StringView txt;
+
+  size_t size() const { return txt.size(); }
+  std::string ToStdString() const { return txt.ToStdString(); }
+};
+
+struct ParserDelegateContext {
+  const DescriptorProto* descriptor;
+  protozero::Message* message;
+  std::set<std::string> seen_fields;
+};
+
+class ParserDelegate {
+ public:
+  ParserDelegate(
+      const DescriptorProto* descriptor,
+      protozero::Message* message,
+      ErrorReporter* reporter,
+      std::map<std::string, const DescriptorProto*> name_to_descriptor,
+      std::map<std::string, const EnumDescriptorProto*> name_to_enum)
+      : reporter_(reporter),
+        name_to_descriptor_(std::move(name_to_descriptor)),
+        name_to_enum_(std::move(name_to_enum)) {
+    ctx_.push(ParserDelegateContext{descriptor, message, {}});
+  }
+
+  void NumericField(Token key, Token value) {
+    const FieldDescriptorProto* field = FindFieldByName(
+        key, value,
+        {
+            FieldDescriptorProto::TYPE_UINT64,
+            FieldDescriptorProto::TYPE_UINT32, FieldDescriptorProto::TYPE_INT64,
+            FieldDescriptorProto::TYPE_SINT64, FieldDescriptorProto::TYPE_INT32,
+            FieldDescriptorProto::TYPE_SINT32,
+            FieldDescriptorProto::TYPE_FIXED64,
+            FieldDescriptorProto::TYPE_SFIXED64,
+            FieldDescriptorProto::TYPE_FIXED32,
+            FieldDescriptorProto::TYPE_SFIXED32,
+            FieldDescriptorProto::TYPE_DOUBLE, FieldDescriptorProto::TYPE_FLOAT,
+        });
+    if (!field)
+      return;
+    const auto& field_type = field->type();
+    switch (field_type) {
+      case FieldDescriptorProto::TYPE_UINT64:
+        return VarIntField<uint64_t>(field, value);
+      case FieldDescriptorProto::TYPE_UINT32:
+        return VarIntField<uint32_t>(field, value);
+      case FieldDescriptorProto::TYPE_INT64:
+      case FieldDescriptorProto::TYPE_SINT64:
+        return VarIntField<int64_t>(field, value);
+      case FieldDescriptorProto::TYPE_INT32:
+      case FieldDescriptorProto::TYPE_SINT32:
+        return VarIntField<int32_t>(field, value);
+
+      case FieldDescriptorProto::TYPE_FIXED64:
+      case FieldDescriptorProto::TYPE_SFIXED64:
+        return FixedField<int64_t>(field, value);
+
+      case FieldDescriptorProto::TYPE_FIXED32:
+      case FieldDescriptorProto::TYPE_SFIXED32:
+        return FixedField<int32_t>(field, value);
+
+      case FieldDescriptorProto::TYPE_DOUBLE:
+        return FixedFloatField<double>(field, value);
+      case FieldDescriptorProto::TYPE_FLOAT:
+        return FixedFloatField<float>(field, value);
+
+      case FieldDescriptorProto::TYPE_BOOL:
+      case FieldDescriptorProto::TYPE_STRING:
+      case FieldDescriptorProto::TYPE_BYTES:
+      case FieldDescriptorProto::TYPE_GROUP:
+      case FieldDescriptorProto::TYPE_MESSAGE:
+      case FieldDescriptorProto::TYPE_ENUM:
+        PERFETTO_FATAL("Invalid type");
+    }
+  }
+
+  void StringField(Token key, Token value) {
+    const FieldDescriptorProto* field = FindFieldByName(
+        key, value,
+        {
+            FieldDescriptorProto::TYPE_STRING, FieldDescriptorProto::TYPE_BYTES,
+        });
+    if (!field)
+      return;
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    const auto& field_type = field->type();
+    PERFETTO_CHECK(field_type == FieldDescriptorProto::TYPE_STRING ||
+                   field_type == FieldDescriptorProto::TYPE_BYTES);
+
+    msg()->AppendBytes(field_id, value.txt.data(), value.size());
+  }
+
+  void IdentifierField(Token key, Token value) {
+    const FieldDescriptorProto* field = FindFieldByName(
+        key, value,
+        {
+            FieldDescriptorProto::TYPE_BOOL, FieldDescriptorProto::TYPE_ENUM,
+        });
+    if (!field)
+      return;
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    const auto& field_type = field->type();
+    if (field_type == FieldDescriptorProto::TYPE_BOOL) {
+      if (value.txt != "true" && value.txt != "false") {
+        AddError(value,
+                 "Expected 'true' or 'false' for boolean field $k in "
+                 "proto $n instead saw '$v'",
+                 std::map<std::string, std::string>{
+                     {"$k", key.ToStdString()},
+                     {"$n", descriptor_name()},
+                     {"$v", value.ToStdString()},
+                 });
+        return;
+      }
+      msg()->AppendTinyVarInt(field_id, value.txt == "true" ? 1 : 0);
+    } else if (field_type == FieldDescriptorProto::TYPE_ENUM) {
+      const std::string& type_name = field->type_name();
+      const EnumDescriptorProto* enum_descriptor = name_to_enum_[type_name];
+      PERFETTO_CHECK(enum_descriptor);
+      bool found_value = false;
+      int32_t enum_value_number = 0;
+      for (const EnumValueDescriptorProto& enum_value :
+           enum_descriptor->value()) {
+        if (value.ToStdString() != enum_value.name())
+          continue;
+        found_value = true;
+        enum_value_number = enum_value.number();
+        break;
+      }
+      PERFETTO_CHECK(found_value);
+      msg()->AppendVarInt<int32_t>(field_id, enum_value_number);
+    } else {
+    }
+  }
+
+  void BeginNestedMessage(Token key, Token value) {
+    const FieldDescriptorProto* field =
+        FindFieldByName(key, value,
+                        {
+                            FieldDescriptorProto::TYPE_MESSAGE,
+                        });
+    if (!field)
+      return;
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    const std::string& type_name = field->type_name();
+    const DescriptorProto* nested_descriptor = name_to_descriptor_[type_name];
+    PERFETTO_CHECK(nested_descriptor);
+    auto* nested_msg = msg()->BeginNestedMessage<protozero::Message>(field_id);
+    ctx_.push(ParserDelegateContext{nested_descriptor, nested_msg, {}});
+  }
+
+  void EndNestedMessage() {
+    msg()->Finalize();
+    ctx_.pop();
+  }
+
+  void Eof() {}
+
+  void AddError(size_t row,
+                size_t column,
+                const char* fmt,
+                const std::map<std::string, std::string>& args) {
+    reporter_->AddError(row, column, 0, Format(fmt, args));
+  }
+
+  void AddError(Token token,
+                const char* fmt,
+                const std::map<std::string, std::string>& args) {
+    reporter_->AddError(token.row, token.column, token.size(),
+                        Format(fmt, args));
+  }
+
+ private:
+  template <typename T>
+  void VarIntField(const FieldDescriptorProto* field, Token t) {
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    uint64_t n = 0;
+    PERFETTO_CHECK(ParseInteger(t.txt, &n));
+    if (field->type() == FieldDescriptorProto::TYPE_SINT64 ||
+        field->type() == FieldDescriptorProto::TYPE_SINT32) {
+      msg()->AppendSignedVarInt<T>(field_id, static_cast<T>(n));
+    } else {
+      msg()->AppendVarInt<T>(field_id, static_cast<T>(n));
+    }
+  }
+
+  template <typename T>
+  void FixedField(const FieldDescriptorProto* field, Token t) {
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    uint64_t n = 0;
+    PERFETTO_CHECK(ParseInteger(t.txt, &n));
+    msg()->AppendFixed<T>(field_id, static_cast<T>(n));
+  }
+
+  template <typename T>
+  void FixedFloatField(const FieldDescriptorProto* field, Token t) {
+    uint32_t field_id = static_cast<uint32_t>(field->number());
+    double n = std::stod(t.ToStdString());
+    msg()->AppendFixed<T>(field_id, static_cast<T>(n));
+  }
+
+  template <typename T>
+  bool ParseInteger(base::StringView s, T* number_ptr) {
+    uint64_t n = 0;
+    PERFETTO_CHECK(sscanf(s.ToStdString().c_str(), "%" PRIu64, &n) == 1);
+    PERFETTO_CHECK(n <= std::numeric_limits<T>::max());
+    *number_ptr = static_cast<T>(n);
+    return true;
+  }
+
+  const FieldDescriptorProto* FindFieldByName(
+      Token key,
+      Token value,
+      std::set<FieldDescriptorProto::Type> valid_field_types) {
+    const std::string field_name = key.ToStdString();
+    const FieldDescriptorProto* field_descriptor = nullptr;
+    for (const auto& f : descriptor()->field()) {
+      if (f.name() == field_name) {
+        field_descriptor = &f;
+        break;
+      }
+    }
+
+    if (!field_descriptor) {
+      AddError(key, "No field named \"$n\" in proto $p",
+               {
+                   {"$n", field_name}, {"$p", descriptor_name()},
+               });
+      return nullptr;
+    }
+
+    bool is_repeated =
+        field_descriptor->label() == FieldDescriptorProto::LABEL_REPEATED;
+    auto it_and_inserted = ctx_.top().seen_fields.emplace(field_name);
+    if (!it_and_inserted.second && !is_repeated) {
+      AddError(key, "Saw non-repeating field '$f' more than once",
+               {
+                   {"$f", field_name},
+               });
+    }
+
+    if (!valid_field_types.count(field_descriptor->type())) {
+      AddError(value,
+               "Expected value of type $t for field $k in proto $n "
+               "instead saw '$v'",
+               {
+                   {"$t", FieldToTypeName(field_descriptor)},
+                   {"$k", field_name},
+                   {"$n", descriptor_name()},
+                   {"$v", value.ToStdString()},
+               });
+      return nullptr;
+    }
+
+    return field_descriptor;
+  }
+
+  const DescriptorProto* descriptor() {
+    PERFETTO_CHECK(!ctx_.empty());
+    return ctx_.top().descriptor;
+  }
+
+  const std::string& descriptor_name() { return descriptor()->name(); }
+
+  protozero::Message* msg() {
+    PERFETTO_CHECK(!ctx_.empty());
+    return ctx_.top().message;
+  }
+
+  std::stack<ParserDelegateContext> ctx_;
+  ErrorReporter* reporter_;
+  std::map<std::string, const DescriptorProto*> name_to_descriptor_;
+  std::map<std::string, const EnumDescriptorProto*> name_to_enum_;
+};
+
+void Parse(const std::string& input, ParserDelegate* delegate) {
+  ParseState state = kWaitingForKey;
+  size_t column = 0;
+  size_t row = 1;
+  size_t depth = 0;
+  bool saw_colon_for_this_key = false;
+  bool saw_semicolon_for_this_value = true;
+  bool comment_till_eol = false;
+  Token key;
+  Token value;
+
+  for (size_t i = 0; i < input.size(); i++, column++) {
+    bool last_character = i + 1 == input.size();
+    char c = input.at(i);
+    if (c == '\n') {
+      column = 0;
+      row++;
+      if (comment_till_eol) {
+        comment_till_eol = false;
+        continue;
+      }
+    }
+    if (comment_till_eol)
+      continue;
+
+    switch (state) {
+      case kWaitingForKey:
+        if (isspace(c))
+          continue;
+        if (c == '#') {
+          comment_till_eol = true;
+          continue;
+        }
+        if (c == '}') {
+          if (depth == 0) {
+            delegate->AddError(row, column, "Unmatched closing brace", {});
+            return;
+          }
+          saw_semicolon_for_this_value = false;
+          depth--;
+          delegate->EndNestedMessage();
+          continue;
+        }
+        if (!saw_semicolon_for_this_value && c == ';') {
+          saw_semicolon_for_this_value = true;
+          continue;
+        }
+        if (IsIdentifierStart(c)) {
+          saw_colon_for_this_key = false;
+          state = kReadingKey;
+          key.offset = i;
+          key.row = row;
+          key.column = column;
+          continue;
+        }
+        break;
+
+      case kReadingKey:
+        if (IsIdentifierBody(c))
+          continue;
+        key.txt = base::StringView(input.data() + key.offset, i - key.offset);
+        state = kWaitingForValue;
+        if (c == '#')
+          comment_till_eol = true;
+        continue;
+
+      case kWaitingForValue:
+        if (isspace(c))
+          continue;
+        if (c == '#') {
+          comment_till_eol = true;
+          continue;
+        }
+        value.offset = i;
+        value.row = row;
+        value.column = column;
+
+        if (c == ':' && !saw_colon_for_this_key) {
+          saw_colon_for_this_key = true;
+          continue;
+        }
+        if (c == '"') {
+          state = kReadingStringValue;
+          continue;
+        }
+        if (c == '-' || isdigit(c)) {
+          state = kReadingNumericValue;
+          continue;
+        }
+        if (IsIdentifierStart(c)) {
+          state = kReadingIdentifierValue;
+          continue;
+        }
+        if (c == '{') {
+          state = kWaitingForKey;
+          depth++;
+          value.txt = base::StringView(input.data() + value.offset, 1);
+          delegate->BeginNestedMessage(key, value);
+          continue;
+        }
+        break;
+
+      case kReadingNumericValue:
+        if (isspace(c) || c == ';' || last_character) {
+          size_t size = i - value.offset + (last_character ? 1 : 0);
+          value.txt = base::StringView(input.data() + value.offset, size);
+          saw_semicolon_for_this_value = c == ';';
+          state = kWaitingForKey;
+          delegate->NumericField(key, value);
+          continue;
+        }
+        if (isdigit(c))
+          continue;
+        break;
+
+      case kReadingStringValue:
+        if (c == '"') {
+          size_t size = i - value.offset - 1;
+          value.column++;
+          value.txt = base::StringView(input.data() + value.offset + 1, size);
+          saw_semicolon_for_this_value = false;
+          state = kWaitingForKey;
+          delegate->StringField(key, value);
+          continue;
+        }
+        continue;
+
+      case kReadingIdentifierValue:
+        if (isspace(c) || c == ';' || c == '#' || last_character) {
+          size_t size = i - value.offset + (last_character ? 1 : 0);
+          value.txt = base::StringView(input.data() + value.offset, size);
+          comment_till_eol = c == '#';
+          saw_semicolon_for_this_value = c == ';';
+          state = kWaitingForKey;
+          delegate->IdentifierField(key, value);
+          continue;
+        }
+        if (IsIdentifierBody(c)) {
+          continue;
+        }
+        break;
+    }
+    PERFETTO_FATAL("Unexpected char %c", c);
+  }  // for
+  if (depth > 0)
+    delegate->AddError(row, column, "Nested message not closed", {});
+  if (state != kWaitingForKey)
+    delegate->AddError(row, column, "Unexpected end of input", {});
+  delegate->Eof();
+}
+
+void AddNestedDescriptors(
+    const std::string& prefix,
+    const DescriptorProto* descriptor,
+    std::map<std::string, const DescriptorProto*>* name_to_descriptor,
+    std::map<std::string, const EnumDescriptorProto*>* name_to_enum) {
+  for (const EnumDescriptorProto& enum_descriptor : descriptor->enum_type()) {
+    const std::string name = prefix + "." + enum_descriptor.name();
+    (*name_to_enum)[name] = &enum_descriptor;
+  }
+  for (const DescriptorProto& nested_descriptor : descriptor->nested_type()) {
+    const std::string name = prefix + "." + nested_descriptor.name();
+    (*name_to_descriptor)[name] = &nested_descriptor;
+    AddNestedDescriptors(name, &nested_descriptor, name_to_descriptor,
+                         name_to_enum);
+  }
+}
+
+}  // namespace
+
+ErrorReporter::ErrorReporter() = default;
+ErrorReporter::~ErrorReporter() = default;
+
+std::vector<uint8_t> PbtxtToPb(const std::string& input,
+                               ErrorReporter* reporter) {
+  std::map<std::string, const DescriptorProto*> name_to_descriptor;
+  std::map<std::string, const EnumDescriptorProto*> name_to_enum;
+  FileDescriptorSet file_descriptor_set;
+
+  {
+    file_descriptor_set.ParseFromArray(
+        kPerfettoConfigDescriptor.data(),
+        static_cast<int>(kPerfettoConfigDescriptor.size()));
+    for (const auto& file_descriptor : file_descriptor_set.file()) {
+      for (const auto& enum_descriptor : file_descriptor.enum_type()) {
+        const std::string name =
+            "." + file_descriptor.package() + "." + enum_descriptor.name();
+        name_to_enum[name] = &enum_descriptor;
+      }
+      for (const auto& descriptor : file_descriptor.message_type()) {
+        const std::string name =
+            "." + file_descriptor.package() + "." + descriptor.name();
+        name_to_descriptor[name] = &descriptor;
+        AddNestedDescriptors(name, &descriptor, &name_to_descriptor,
+                             &name_to_enum);
+      }
+    }
+  }
+
+  const DescriptorProto* descriptor = name_to_descriptor[kConfigProtoName];
+  PERFETTO_CHECK(descriptor);
+
+  ScatteredStreamMemoryDelegate stream_delegate(base::kPageSize);
+  protozero::ScatteredStreamWriter stream(&stream_delegate);
+  stream_delegate.set_writer(&stream);
+
+  protozero::Message message;
+  message.Reset(&stream);
+  ParserDelegate delegate(descriptor, &message, reporter,
+                          std::move(name_to_descriptor),
+                          std::move(name_to_enum));
+  Parse(input, &delegate);
+  return stream_delegate.StitchChunks();
+}
+
+}  // namespace perfetto
diff --git a/src/perfetto_cmd/pbtxt_to_pb.h b/src/perfetto_cmd/pbtxt_to_pb.h
new file mode 100644
index 0000000..6b9db32
--- /dev/null
+++ b/src/perfetto_cmd/pbtxt_to_pb.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PERFETTO_CMD_PBTXT_TO_PB_H_
+#define SRC_PERFETTO_CMD_PBTXT_TO_PB_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+namespace perfetto {
+
+class ErrorReporter {
+ public:
+  ErrorReporter();
+  virtual ~ErrorReporter();
+  virtual void AddError(size_t row,
+                        size_t column,
+                        size_t size,
+                        const std::string& message) = 0;
+};
+
+std::vector<uint8_t> PbtxtToPb(const std::string& input,
+                               ErrorReporter* reporter);
+
+}  // namespace perfetto
+
+#endif  // SRC_PERFETTO_CMD_PBTXT_TO_PB_H_
diff --git a/src/perfetto_cmd/pbtxt_to_pb_unittest.cc b/src/perfetto_cmd/pbtxt_to_pb_unittest.cc
new file mode 100644
index 0000000..00e2e3c
--- /dev/null
+++ b/src/perfetto_cmd/pbtxt_to_pb_unittest.cc
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/perfetto_cmd/pbtxt_to_pb.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "perfetto/config/trace_config.pb.h"
+
+namespace perfetto {
+namespace {
+
+using ::testing::StrictMock;
+using ::testing::ElementsAre;
+using ::google::protobuf::io::ZeroCopyInputStream;
+using ::google::protobuf::io::ArrayInputStream;
+
+class MockErrorReporter : public ErrorReporter {
+ public:
+  MockErrorReporter() {}
+  ~MockErrorReporter() = default;
+  MOCK_METHOD4(AddError,
+               void(size_t line,
+                    size_t column_start,
+                    size_t column_end,
+                    const std::string& message));
+};
+
+protos::TraceConfig ToProto(const std::string& input) {
+  StrictMock<MockErrorReporter> reporter;
+  std::vector<uint8_t> output = PbtxtToPb(input, &reporter);
+  EXPECT_FALSE(output.empty());
+  protos::TraceConfig config;
+  config.ParseFromArray(output.data(), static_cast<int>(output.size()));
+  return config;
+}
+
+void ToErrors(const std::string& input, MockErrorReporter* reporter) {
+  std::vector<uint8_t> output = PbtxtToPb(input, reporter);
+}
+
+TEST(PbtxtToPb, OneField) {
+  protos::TraceConfig config = ToProto(R"(
+    duration_ms: 1234
+  )");
+  EXPECT_EQ(config.duration_ms(), 1234);
+}
+
+TEST(PbtxtToPb, TwoFields) {
+  protos::TraceConfig config = ToProto(R"(
+    duration_ms: 1234
+    file_write_period_ms: 5678
+  )");
+  EXPECT_EQ(config.duration_ms(), 1234);
+  EXPECT_EQ(config.file_write_period_ms(), 5678);
+}
+
+TEST(PbtxtToPb, Semicolons) {
+  protos::TraceConfig config = ToProto(R"(
+    duration_ms: 1234;
+    file_write_period_ms: 5678;
+  )");
+  EXPECT_EQ(config.duration_ms(), 1234);
+  EXPECT_EQ(config.file_write_period_ms(), 5678);
+}
+
+TEST(PbtxtToPb, NestedMessage) {
+  protos::TraceConfig config = ToProto(R"(
+    buffers: {
+      size_kb: 123
+    }
+  )");
+  ASSERT_EQ(config.buffers().size(), 1);
+  EXPECT_EQ(config.buffers().Get(0).size_kb(), 123);
+}
+
+TEST(PbtxtToPb, SplitNested) {
+  protos::TraceConfig config = ToProto(R"(
+    buffers: {
+      size_kb: 1
+    }
+    duration_ms: 1000;
+    buffers: {
+      size_kb: 2
+    }
+  )");
+  ASSERT_EQ(config.buffers().size(), 2);
+  EXPECT_EQ(config.buffers().Get(0).size_kb(), 1);
+  EXPECT_EQ(config.buffers().Get(1).size_kb(), 2);
+  EXPECT_EQ(config.duration_ms(), 1000);
+}
+
+TEST(PbtxtToPb, MultipleNestedMessage) {
+  protos::TraceConfig config = ToProto(R"(
+    buffers: {
+      size_kb: 1
+    }
+    buffers: {
+      size_kb: 2
+    }
+  )");
+  ASSERT_EQ(config.buffers().size(), 2);
+  EXPECT_EQ(config.buffers().Get(0).size_kb(), 1);
+  EXPECT_EQ(config.buffers().Get(1).size_kb(), 2);
+}
+
+TEST(PbtxtToPb, NestedMessageCrossFile) {
+  protos::TraceConfig config = ToProto(R"(
+data_sources {
+  config {
+    ftrace_config {
+      drain_period_ms: 42
+    }
+  }
+}
+  )");
+  ASSERT_EQ(
+      config.data_sources().Get(0).config().ftrace_config().drain_period_ms(),
+      42);
+}
+
+TEST(PbtxtToPb, Booleans) {
+  protos::TraceConfig config = ToProto(R"(
+    write_into_file: false; deferred_start: true;
+  )");
+  EXPECT_EQ(config.write_into_file(), false);
+  EXPECT_EQ(config.deferred_start(), true);
+}
+
+TEST(PbtxtToPb, Comments) {
+  protos::TraceConfig config = ToProto(R"(
+    write_into_file: false # deferred_start: true;
+    buffers# 1
+    # 2
+    :# 3
+    # 4
+    {# 5
+    # 6
+    fill_policy# 7
+    # 8
+    :# 9
+    # 10
+    RING_BUFFER# 11
+    # 12
+    ;# 13
+    # 14
+    } # 15
+    # 16
+  )");
+  EXPECT_EQ(config.write_into_file(), false);
+  EXPECT_EQ(config.deferred_start(), false);
+}
+
+TEST(PbtxtToPb, Enums) {
+  protos::TraceConfig config = ToProto(R"(
+    buffers: {
+      fill_policy: RING_BUFFER
+    }
+  )");
+  EXPECT_EQ(config.buffers().Get(0).fill_policy(),
+            protos::TraceConfig::BufferConfig::RING_BUFFER);
+}
+
+TEST(PbtxtToPb, AllFieldTypes) {
+  protos::TraceConfig config = ToProto(R"(
+data_sources {
+  config {
+    for_testing {
+      dummy_fields {
+        field_uint32: 1;
+        field_uint64: 2;
+        field_int32: 3;
+        field_int64: 4;
+        field_fixed64: 5;
+        field_sfixed64: 6;
+        field_fixed32: 7;
+        field_sfixed32: 8;
+        field_double: 9;
+        field_float: 10;
+        field_sint64: 11;
+        field_sint32: 12;
+        field_string: "13";
+        field_bytes: "14";
+      }
+    }
+  }
+}
+  )");
+  const auto& fields =
+      config.data_sources().Get(0).config().for_testing().dummy_fields();
+  ASSERT_EQ(fields.field_uint32(), 1);
+  ASSERT_EQ(fields.field_uint64(), 2);
+  ASSERT_EQ(fields.field_int32(), 3);
+  ASSERT_EQ(fields.field_int64(), 4);
+  ASSERT_EQ(fields.field_fixed64(), 5);
+  ASSERT_EQ(fields.field_sfixed64(), 6);
+  ASSERT_EQ(fields.field_fixed32(), 7);
+  ASSERT_EQ(fields.field_sfixed32(), 8);
+  ASSERT_EQ(fields.field_double(), 9);
+  ASSERT_EQ(fields.field_float(), 10);
+  ASSERT_EQ(fields.field_sint64(), 11);
+  ASSERT_EQ(fields.field_sint32(), 12);
+  ASSERT_EQ(fields.field_string(), "13");
+  ASSERT_EQ(fields.field_bytes(), "14");
+}
+
+TEST(PbtxtToPb, NegativeNumbers) {
+  protos::TraceConfig config = ToProto(R"(
+data_sources {
+  config {
+    for_testing {
+      dummy_fields {
+        field_int32: -1;
+        field_int64: -2;
+        field_fixed64: -3;
+        field_sfixed64: -4;
+        field_fixed32: -5;
+        field_sfixed32: -6;
+        field_double: -7;
+        field_float: -8;
+        field_sint64: -9;
+        field_sint32: -10;
+      }
+    }
+  }
+}
+  )");
+  const auto& fields =
+      config.data_sources().Get(0).config().for_testing().dummy_fields();
+  ASSERT_EQ(fields.field_int32(), -1);
+  ASSERT_EQ(fields.field_int64(), -2);
+  ASSERT_EQ(fields.field_fixed64(), -3);
+  ASSERT_EQ(fields.field_sfixed64(), -4);
+  ASSERT_EQ(fields.field_fixed32(), -5);
+  ASSERT_EQ(fields.field_sfixed32(), -6);
+  ASSERT_EQ(fields.field_double(), -7);
+  ASSERT_EQ(fields.field_float(), -8);
+  ASSERT_EQ(fields.field_sint64(), -9);
+  ASSERT_EQ(fields.field_sint32(), -10);
+}
+
+TEST(PbtxtToPb, EofEndsNumeric) {
+  protos::TraceConfig config = ToProto(R"(duration_ms: 1234)");
+  EXPECT_EQ(config.duration_ms(), 1234);
+}
+
+TEST(PbtxtToPb, EofEndsIdentifier) {
+  protos::TraceConfig config = ToProto(R"(enable_extra_guardrails: true)");
+  EXPECT_EQ(config.enable_extra_guardrails(), true);
+}
+
+TEST(PbtxtToPb, ExampleConfig) {
+  protos::TraceConfig config = ToProto(R"(
+buffers {
+  size_kb: 100024
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      buffer_size_kb: 512 # 4 (page size) * 128
+      drain_period_ms: 200
+      ftrace_events: "binder_lock"
+      ftrace_events: "binder_locked"
+      atrace_categories: "gfx"
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.inode_file_map"
+    target_buffer: 0
+    inode_file_config {
+      scan_delay_ms: 1000
+      scan_interval_ms: 1000
+      scan_batch_size: 500
+      mount_point_mapping: {
+        mountpoint: "/data"
+        scan_roots: "/data/app"
+      }
+    }
+  }
+}
+
+producers {
+  producer_name: "perfetto.traced_probes"
+  shm_size_kb: 4096
+  page_size_kb: 4
+}
+
+duration_ms: 10000
+)");
+  EXPECT_EQ(config.duration_ms(), 10000);
+  EXPECT_EQ(config.buffers().Get(0).size_kb(), 100024);
+  EXPECT_EQ(config.data_sources().Get(0).config().name(), "linux.ftrace");
+  EXPECT_EQ(config.data_sources().Get(0).config().target_buffer(), 0);
+  EXPECT_EQ(config.producers().Get(0).producer_name(),
+            "perfetto.traced_probes");
+}
+
+TEST(PbtxtToPb, UnknownField) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter,
+              AddError(2, 5, 11,
+                       "No field named \"not_a_label\" in proto TraceConfig"));
+  ToErrors(R"(
+    not_a_label: false
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, UnknownNestedField) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(
+      reporter,
+      AddError(
+          4, 5, 16,
+          "No field named \"not_a_field_name\" in proto DataSourceConfig"));
+  ToErrors(R"(
+data_sources {
+  config {
+    not_a_field_name {
+    }
+  }
+}
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, BadBoolean) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter, AddError(2, 22, 3,
+                                 "Expected 'true' or 'false' for boolean field "
+                                 "write_into_file in proto TraceConfig instead "
+                                 "saw 'foo'"));
+  ToErrors(R"(
+    write_into_file: foo;
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, MissingBoolean) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter, AddError(3, 3, 0, "Unexpected end of input"));
+  ToErrors(R"(
+    write_into_file:
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, RootProtoMustNotEndWithBrace) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter, AddError(2, 5, 0, "Unmatched closing brace"));
+  ToErrors(R"(
+    }
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, SawNonRepeatedFieldTwice) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(
+      reporter,
+      AddError(3, 5, 15,
+               "Saw non-repeating field 'write_into_file' more than once"));
+  ToErrors(R"(
+    write_into_file: true;
+    write_into_file: true;
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, WrongTypeBoolean) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter,
+              AddError(2, 18, 4,
+                       "Expected value of type uint32 for field duration_ms in "
+                       "proto TraceConfig instead saw 'true'"));
+  ToErrors(R"(
+    duration_ms: true;
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, WrongTypeNumber) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter,
+              AddError(2, 14, 3,
+                       "Expected value of type message for field buffers in "
+                       "proto TraceConfig instead saw '100'"));
+  ToErrors(R"(
+    buffers: 100;
+  )",
+           &reporter);
+}
+
+TEST(PbtxtToPb, NestedMessageDidNotTerminate) {
+  MockErrorReporter reporter;
+  EXPECT_CALL(reporter, AddError(2, 15, 0, "Nested message not closed"));
+  ToErrors(R"(
+    buffers: {)",
+           &reporter);
+}
+
+// TODO(hjd): Add these tests.
+// TEST(PbtxtToPb, WrongTypeString)
+// TEST(PbtxtToPb, OverflowOnIntegers)
+// TEST(PbtxtToPb, NegativeNumbersForUnsignedInt)
+// TEST(PbtxtToPb, UnterminatedString) {
+// TEST(PbtxtToPb, NumberIsEof)
+// TEST(PbtxtToPb, EscapedQuotes)
+// TEST(PbtxtToPb, OneOf)
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 8c8f20e..54ab538 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -31,6 +31,7 @@
 
 #include "perfetto/base/file_utils.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/base/string_view.h"
 #include "perfetto/base/time.h"
 #include "perfetto/base/utils.h"
 #include "perfetto/protozero/proto_utils.h"
@@ -39,6 +40,7 @@
 #include "perfetto/tracing/core/data_source_descriptor.h"
 #include "perfetto/tracing/core/trace_config.h"
 #include "perfetto/tracing/core/trace_packet.h"
+#include "src/perfetto_cmd/pbtxt_to_pb.h"
 
 #include "perfetto/config/trace_config.pb.h"
 
@@ -59,6 +61,64 @@
 
 perfetto::PerfettoCmd* g_consumer_cmd;
 
+class LoggingErrorReporter : public ErrorReporter {
+ public:
+  LoggingErrorReporter(std::string file_name, const char* config)
+      : file_name_(file_name), config_(config) {}
+
+  void AddError(size_t row,
+                size_t column,
+                size_t length,
+                const std::string& message) override {
+    parsed_successfully_ = false;
+    std::string line = ExtractLine(row - 1).ToStdString();
+    if (!line.empty() && line[line.length() - 1] == '\n') {
+      line.erase(line.length() - 1);
+    }
+
+    std::string guide(column + length, ' ');
+    for (size_t i = column; i < column + length; i++) {
+      guide[i - 1] = i == column ? '^' : '~';
+    }
+    fprintf(stderr, "%s:%zu:%zu error: %s\n", file_name_.c_str(), row, column,
+            message.c_str());
+    fprintf(stderr, "%s\n", line.c_str());
+    fprintf(stderr, "%s\n", guide.c_str());
+  }
+
+  bool Success() const { return parsed_successfully_; }
+
+ private:
+  base::StringView ExtractLine(size_t line) {
+    const char* start = config_;
+    const char* end = config_;
+
+    for (size_t i = 0; i < line + 1; i++) {
+      start = end;
+      char c;
+      while ((c = *end++) && c != '\n')
+        ;
+    }
+    return base::StringView(start, static_cast<size_t>(end - start));
+  }
+
+  bool parsed_successfully_ = true;
+  std::string file_name_;
+  const char* config_;
+};
+
+bool ParseTraceConfigPbtxt(const std::string& file_name,
+                           const std::string& pbtxt,
+                           protos::TraceConfig* config) {
+  LoggingErrorReporter reporter(file_name, pbtxt.c_str());
+  std::vector<uint8_t> buf = PbtxtToPb(pbtxt, &reporter);
+  if (!reporter.Success())
+    return false;
+  if (!config->ParseFromArray(buf.data(), static_cast<int>(buf.size())))
+    return false;
+  return true;
+}
+
 }  // namespace
 
 // Temporary directory for DropBox traces. Note that this is automatically
@@ -77,6 +137,7 @@
   --dropbox        -d TAG : Upload trace into DropBox using tag TAG (default: %s)
   --no-guardrails  -n     : Ignore guardrails triggered when using --dropbox (for testing).
   --reset-guardrails      : Resets the state of the guardails and exits (for testing).
+  --txt            -t     : Parse config as pbtxt. Not a stable API. Not for production use.
   --help           -h
 
 statsd-specific flags:
@@ -103,6 +164,7 @@
       {"background", no_argument, nullptr, 'b'},
       {"dropbox", optional_argument, nullptr, 'd'},
       {"no-guardrails", optional_argument, nullptr, 'n'},
+      {"txt", optional_argument, nullptr, 't'},
       {"alert-id", required_argument, nullptr, OPT_ALERT_ID},
       {"config-id", required_argument, nullptr, OPT_CONFIG_ID},
       {"config-uid", required_argument, nullptr, OPT_CONFIG_UID},
@@ -110,20 +172,23 @@
       {nullptr, 0, nullptr, 0}};
 
   int option_index = 0;
+  std::string config_file_name;
   std::string trace_config_raw;
   bool background = false;
   bool ignore_guardrails = false;
+  bool parse_as_pbtxt = false;
   perfetto::protos::TraceConfig::StatsdMetadata statsd_metadata;
   RateLimiter limiter;
 
   for (;;) {
     int option =
-        getopt_long(argc, argv, "c:o:bd::n", long_options, &option_index);
+        getopt_long(argc, argv, "c:o:bd::nt", long_options, &option_index);
 
     if (option == -1)
       break;  // EOF.
 
     if (option == 'c') {
+      config_file_name = std::string(optarg);
       if (strcmp(optarg, "-") == 0) {
         std::istreambuf_iterator<char> begin(std::cin), end;
         trace_config_raw.assign(begin, end);
@@ -173,6 +238,11 @@
       continue;
     }
 
+    if (option == 't') {
+      parse_as_pbtxt = true;
+      continue;
+    }
+
     if (option == OPT_RESET_GUARDRAILS) {
       PERFETTO_CHECK(limiter.ClearState());
       PERFETTO_ILOG("Guardrail state cleared");
@@ -215,7 +285,14 @@
 
   perfetto::protos::TraceConfig trace_config_proto;
   PERFETTO_DLOG("Parsing TraceConfig, %zu bytes", trace_config_raw.size());
-  bool parsed = trace_config_proto.ParseFromString(trace_config_raw);
+  bool parsed;
+  if (parse_as_pbtxt) {
+    parsed = ParseTraceConfigPbtxt(config_file_name, trace_config_raw,
+                                   &trace_config_proto);
+  } else {
+    parsed = trace_config_proto.ParseFromString(trace_config_raw);
+  }
+
   if (!parsed) {
     PERFETTO_ELOG("Could not parse TraceConfig proto");
     return 1;
@@ -321,8 +398,15 @@
   if (dropbox_tag_.empty()) {
     trace_out_stream_.reset();
     did_process_full_trace_ = true;
-    PERFETTO_ILOG("Wrote %" PRIu64 " bytes into %s", bytes_written_,
-                  trace_out_path_ == "-" ? "stdout" : trace_out_path_.c_str());
+    if (trace_config_->write_into_file()) {
+      PERFETTO_ILOG("Streamed output to %s", trace_out_path_ == "-"
+                                                 ? "stdout"
+                                                 : trace_out_path_.c_str());
+    } else {
+      PERFETTO_ILOG(
+          "Wrote %" PRIu64 " bytes into %s", bytes_written_,
+          trace_out_path_ == "-" ? "stdout" : trace_out_path_.c_str());
+    }
   } else {
 #if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
     android::sp<android::os::DropBoxManager> dropbox =
diff --git a/src/perfetto_cmd/perfetto_config.descriptor.h b/src/perfetto_cmd/perfetto_config.descriptor.h
new file mode 100644
index 0000000..211e57c
--- /dev/null
+++ b/src/perfetto_cmd/perfetto_config.descriptor.h
@@ -0,0 +1,735 @@
+
+#ifndef SRC_PERFETTO_CMD_PERFETTO_CONFIG_DESCRIPTOR_H_
+#define SRC_PERFETTO_CMD_PERFETTO_CONFIG_DESCRIPTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
+
+// SHA1(tools/gen_binary_descriptors)
+// 39b24672017b75f5e5ac590c4dc03b2c41bf3eea
+// SHA1(protos/perfetto/config/perfetto_config.proto)
+// 5df0ef2a9ce882bdf0bda51d19c9fbd77fabc107
+
+// This is the proto {proto_name} encoded as a ProtoFileDescriptor to allow
+// for reflection without libprotobuf full/non-lite protos.
+
+namespace perfetto {
+
+constexpr std::array<uint8_t, 8501> kPerfettoConfigDescriptor{
+    {0x0a, 0xb2, 0x42, 0x0a, 0x25, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22,
+     0x31, 0x0a, 0x0c, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x09, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x22, 0xd1, 0x05, 0x0a, 0x10, 0x44, 0x61, 0x74, 0x61, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74,
+     0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72,
+     0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67,
+     0x65, 0x74, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
+     0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
+     0x0f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69,
+     0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x63,
+     0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
+     0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x74, 0x72,
+     0x61, 0x63, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+     0x49, 0x64, 0x12, 0x42, 0x0a, 0x0d, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65,
+     0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x64, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x46, 0x74, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x66, 0x74,
+     0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42,
+     0x0a, 0x0d, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x11, 0x69, 0x6e,
+     0x6f, 0x64, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0f, 0x69, 0x6e, 0x6f,
+     0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x12, 0x55, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f,
+     0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+     0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x65, 0x72,
+     0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
+     0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74,
+     0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x12, 0x70, 0x72, 0x6f,
+     0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x49, 0x0a, 0x10, 0x73, 0x79, 0x73, 0x5f, 0x73,
+     0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
+     0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66,
+     0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e,
+     0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x52, 0x0e, 0x73, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4b, 0x0a, 0x10, 0x68, 0x65,
+     0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70,
+     0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+     0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0f, 0x68, 0x65, 0x61, 0x70,
+     0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x24, 0x0a, 0x0d, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x63, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
+     0x0c, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x12, 0x3f, 0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x73,
+     0x74, 0x69, 0x6e, 0x67, 0x18, 0xff, 0xff, 0xff, 0x7f, 0x20, 0x01, 0x28,
+     0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x65, 0x73, 0x74,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x54,
+     0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xcf, 0x01, 0x0a, 0x0c, 0x46,
+     0x74, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x23, 0x0a, 0x0d, 0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x65, 0x76,
+     0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,
+     0x66, 0x74, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
+     0x12, 0x2b, 0x0a, 0x11, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63,
+     0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20,
+     0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x0a,
+     0x0b, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x70, 0x70, 0x73,
+     0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x72, 0x61,
+     0x63, 0x65, 0x41, 0x70, 0x70, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x75,
+     0x66, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62,
+     0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x75, 0x66, 0x66,
+     0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x62, 0x12, 0x26, 0x0a, 0x0f,
+     0x64, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64,
+     0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64,
+     0x72, 0x61, 0x69, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73,
+     0x22, 0x95, 0x03, 0x0a, 0x0f, 0x49, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x69,
+     0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a, 0x10,
+     0x73, 0x63, 0x61, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
+     0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e,
+     0x73, 0x63, 0x61, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+     0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x64,
+     0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x0b, 0x73, 0x63, 0x61, 0x6e, 0x44, 0x65, 0x6c, 0x61, 0x79,
+     0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x62,
+     0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20,
+     0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x63, 0x61, 0x6e, 0x42, 0x61, 0x74,
+     0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x6f,
+     0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x18, 0x04, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x53, 0x63,
+     0x61, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x6d,
+     0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18,
+     0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x61, 0x6e, 0x4d,
+     0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x67,
+     0x0a, 0x13, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e,
+     0x74, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20,
+     0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x49, 0x6e,
+     0x6f, 0x64, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+     0x67, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74,
+     0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79,
+     0x52, 0x11, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74,
+     0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x1a, 0x57, 0x0a, 0x16, 0x4d,
+     0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x70,
+     0x70, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1e, 0x0a,
+     0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
+     0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
+     0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x61,
+     0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+     0x09, 0x52, 0x09, 0x73, 0x63, 0x61, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x73,
+     0x22, 0xca, 0x02, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+     0x42, 0x0a, 0x06, 0x71, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x18, 0x01, 0x20,
+     0x03, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x72,
+     0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x2e, 0x51, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x52,
+     0x06, 0x71, 0x75, 0x69, 0x72, 0x6b, 0x73, 0x12, 0x3c, 0x0a, 0x1b, 0x73,
+     0x63, 0x61, 0x6e, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x63,
+     0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61,
+     0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x73, 0x63,
+     0x61, 0x6e, 0x41, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+     0x65, 0x73, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2e, 0x0a,
+     0x13, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65,
+     0x61, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01,
+     0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x68,
+     0x72, 0x65, 0x61, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2b, 0x0a,
+     0x12, 0x70, 0x72, 0x6f, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f,
+     0x70, 0x6f, 0x6c, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x53, 0x74, 0x61, 0x74, 0x73,
+     0x50, 0x6f, 0x6c, 0x6c, 0x4d, 0x73, 0x22, 0x55, 0x0a, 0x06, 0x51, 0x75,
+     0x69, 0x72, 0x6b, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x51, 0x55, 0x49, 0x52,
+     0x4b, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+     0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x14, 0x44, 0x49, 0x53, 0x41,
+     0x42, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f,
+     0x44, 0x55, 0x4d, 0x50, 0x10, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x15,
+     0x0a, 0x11, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x4f, 0x4e,
+     0x5f, 0x44, 0x45, 0x4d, 0x41, 0x4e, 0x44, 0x10, 0x02, 0x22, 0xf3, 0x03,
+     0x0a, 0x0e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x6d, 0x69,
+     0x6e, 0x66, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d,
+     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x65, 0x6d,
+     0x69, 0x6e, 0x66, 0x6f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73,
+     0x12, 0x4b, 0x0a, 0x10, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x5f,
+     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03,
+     0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6d,
+     0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
+     0x52, 0x0f, 0x6d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75,
+     0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x76, 0x6d, 0x73,
+     0x74, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d,
+     0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x76, 0x6d, 0x73,
+     0x74, 0x61, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12,
+     0x48, 0x0a, 0x0f, 0x76, 0x6d, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x63, 0x6f,
+     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e,
+     0x32, 0x1f, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x6d, 0x73, 0x74, 0x61,
+     0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x76,
+     0x6d, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
+     0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x70, 0x65,
+     0x72, 0x69, 0x6f, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f,
+     0x64, 0x4d, 0x73, 0x12, 0x51, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x5f,
+     0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
+     0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x53, 0x79, 0x73,
+     0x53, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
+     0x53, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
+     0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
+     0x72, 0x73, 0x22, 0x7b, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x43, 0x6f,
+     0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+     0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x43, 0x50, 0x55, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x10, 0x01,
+     0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x49, 0x52, 0x51,
+     0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x53, 0x10, 0x02, 0x12, 0x17, 0x0a,
+     0x13, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x53, 0x4f, 0x46, 0x54, 0x49, 0x52,
+     0x51, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x53, 0x10, 0x03, 0x12, 0x13,
+     0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x46, 0x4f, 0x52, 0x4b, 0x5f,
+     0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x22, 0x9e, 0x06, 0x0a, 0x0a,
+     0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23,
+     0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f,
+     0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d,
+     0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
+     0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
+     0x67, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f,
+     0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61,
+     0x78, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x50, 0x65, 0x72,
+     0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65,
+     0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x65,
+     0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+     0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a,
+     0x65, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x62, 0x61,
+     0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73,
+     0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x73,
+     0x65, 0x6e, 0x64, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x52, 0x65,
+     0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x0c, 0x64, 0x75,
+     0x6d, 0x6d, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x06,
+     0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65,
+     0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54,
+     0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x75,
+     0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x0b, 0x64,
+     0x75, 0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0xfb,
+     0x03, 0x0a, 0x0b, 0x44, 0x75, 0x6d, 0x6d, 0x79, 0x46, 0x69, 0x65, 0x6c,
+     0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
+     0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
+     0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x55, 0x69, 0x6e, 0x74, 0x33,
+     0x32, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69,
+     0x6e, 0x74, 0x33, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a,
+     0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x21,
+     0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75, 0x69, 0x6e, 0x74,
+     0x36, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x69,
+     0x65, 0x6c, 0x64, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x1f, 0x0a,
+     0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34,
+     0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c,
+     0x64, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69,
+     0x65, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x18,
+     0x05, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64,
+     0x46, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x12, 0x25, 0x0a, 0x0e, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36,
+     0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x10, 0x52, 0x0d, 0x66, 0x69, 0x65,
+     0x6c, 0x64, 0x53, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x12, 0x23,
+     0x0a, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x78, 0x65,
+     0x64, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x07, 0x52, 0x0c, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x46, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x12,
+     0x25, 0x0a, 0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x66, 0x69,
+     0x78, 0x65, 0x64, 0x33, 0x32, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0f, 0x52,
+     0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x66, 0x69, 0x78, 0x65, 0x64,
+     0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
+     0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01,
+     0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x6f, 0x75, 0x62, 0x6c,
+     0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x66,
+     0x6c, 0x6f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a,
+     0x66, 0x69, 0x65, 0x6c, 0x64, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x21,
+     0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x6e, 0x74,
+     0x36, 0x34, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x12, 0x52, 0x0b, 0x66, 0x69,
+     0x65, 0x6c, 0x64, 0x53, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x21, 0x0a,
+     0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x6e, 0x74, 0x33,
+     0x32, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x11, 0x52, 0x0b, 0x66, 0x69, 0x65,
+     0x6c, 0x64, 0x53, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c,
+     0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+     0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c,
+     0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x66,
+     0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0e,
+     0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42,
+     0x79, 0x74, 0x65, 0x73, 0x22, 0x81, 0x0c, 0x0a, 0x0b, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x07,
+     0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+     0x0b, 0x32, 0x29, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x66, 0x66,
+     0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75,
+     0x66, 0x66, 0x65, 0x72, 0x73, 0x12, 0x4a, 0x0a, 0x0c, 0x64, 0x61, 0x74,
+     0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20,
+     0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74,
+     0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72,
+     0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x61,
+     0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0b, 0x64, 0x61,
+     0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a,
+     0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73,
+     0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61,
+     0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x65, 0x6e,
+     0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x67,
+     0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20,
+     0x01, 0x28, 0x08, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45,
+     0x78, 0x74, 0x72, 0x61, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69,
+     0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x64, 0x6f,
+     0x77, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+     0x0e, 0x32, 0x32, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f,
+     0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63,
+     0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x6f, 0x63, 0x6b,
+     0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65, 0x72,
+     0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x64,
+     0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x49, 0x0a, 0x09, 0x70,
+     0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
+     0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74,
+     0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61,
+     0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f,
+     0x64, 0x75, 0x63, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+     0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x73, 0x12, 0x54,
+     0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+     0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
+     0x2b, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70,
+     0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43,
+     0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x64,
+     0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x73, 0x74,
+     0x61, 0x74, 0x73, 0x64, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+     0x12, 0x26, 0x0a, 0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6e,
+     0x74, 0x6f, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
+     0x08, 0x52, 0x0d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x6f,
+     0x46, 0x69, 0x6c, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x66, 0x69, 0x6c, 0x65,
+     0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f,
+     0x64, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11,
+     0x66, 0x69, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x50, 0x65, 0x72,
+     0x69, 0x6f, 0x64, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78,
+     0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62,
+     0x79, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10,
+     0x6d, 0x61, 0x78, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x42,
+     0x79, 0x74, 0x65, 0x73, 0x12, 0x60, 0x0a, 0x13, 0x67, 0x75, 0x61, 0x72,
+     0x64, 0x72, 0x61, 0x69, 0x6c, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69,
+     0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e,
+     0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
+     0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x2e, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69,
+     0x6c, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x52, 0x12,
+     0x67, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c, 0x4f, 0x76, 0x65,
+     0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65,
+     0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74,
+     0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x65,
+     0x72, 0x72, 0x65, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x1a, 0xba, 0x01,
+     0x0a, 0x0c, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b,
+     0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x69, 0x7a,
+     0x65, 0x4b, 0x62, 0x12, 0x55, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x6c, 0x5f,
+     0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
+     0x32, 0x34, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x66, 0x66, 0x65,
+     0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x46, 0x69, 0x6c, 0x6c,
+     0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x6c,
+     0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x2e, 0x0a, 0x0a, 0x46, 0x69,
+     0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x0f, 0x0a, 0x0b,
+     0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+     0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x42, 0x55,
+     0x46, 0x46, 0x45, 0x52, 0x10, 0x01, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03,
+     0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x1a, 0x79, 0x0a, 0x0a, 0x44, 0x61,
+     0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x06,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x21, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53,
+     0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+     0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x70,
+     0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
+     0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28,
+     0x09, 0x52, 0x12, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x4e,
+     0x61, 0x6d, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x77, 0x0a,
+     0x0e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x64, 0x75,
+     0x63, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
+     0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72,
+     0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0b, 0x73, 0x68, 0x6d, 0x5f,
+     0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6b, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28,
+     0x0d, 0x52, 0x09, 0x73, 0x68, 0x6d, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x62,
+     0x12, 0x20, 0x0a, 0x0c, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a,
+     0x65, 0x5f, 0x6b, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a,
+     0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x4b, 0x62, 0x1a, 0xa6,
+     0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x64, 0x4d, 0x65, 0x74,
+     0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x72, 0x69,
+     0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x65, 0x72,
+     0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11,
+     0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x6c,
+     0x65, 0x72, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x72, 0x69,
+     0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
+     0x69, 0x67, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
+     0x52, 0x13, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x69, 0x64, 0x12, 0x30, 0x0a,
+     0x14, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
+     0x01, 0x28, 0x03, 0x52, 0x12, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72,
+     0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x1a,
+     0x4c, 0x0a, 0x12, 0x47, 0x75, 0x61, 0x72, 0x64, 0x72, 0x61, 0x69, 0x6c,
+     0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x73, 0x12, 0x36, 0x0a,
+     0x18, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f,
+     0x70, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65,
+     0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x6d, 0x61, 0x78,
+     0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x65, 0x72, 0x44, 0x61, 0x79,
+     0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x55, 0x0a, 0x15, 0x4c, 0x6f, 0x63,
+     0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x4f, 0x70, 0x65,
+     0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x4f,
+     0x43, 0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x55, 0x4e, 0x43, 0x48, 0x41,
+     0x4e, 0x47, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f,
+     0x43, 0x4b, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52,
+     0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x4f, 0x43, 0x4b, 0x44, 0x4f,
+     0x57, 0x4e, 0x5f, 0x53, 0x45, 0x54, 0x10, 0x02, 0x22, 0xda, 0x02, 0x0a,
+     0x0f, 0x48, 0x65, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x61, 0x6d, 0x70,
+     0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
+     0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+     0x04, 0x52, 0x15, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x49,
+     0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73,
+     0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x62,
+     0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,
+     0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65,
+     0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10,
+     0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x04, 0x52,
+     0x03, 0x70, 0x69, 0x64, 0x12, 0x6a, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x74,
+     0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f,
+     0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+     0x32, 0x34, 0x2e, 0x70, 0x65, 0x72, 0x66, 0x65, 0x74, 0x74, 0x6f, 0x2e,
+     0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x70, 0x70,
+     0x72, 0x6f, 0x66, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43,
+     0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70,
+     0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x14, 0x63, 0x6f, 0x6e, 0x74,
+     0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f,
+     0x6e, 0x66, 0x69, 0x67, 0x1a, 0x63, 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x74,
+     0x69, 0x6e, 0x6f, 0x75, 0x73, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, 0x6e,
+     0x66, 0x69, 0x67, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x75, 0x6d, 0x70, 0x5f,
+     0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01,
+     0x28, 0x0d, 0x52, 0x0b, 0x64, 0x75, 0x6d, 0x70, 0x50, 0x68, 0x61, 0x73,
+     0x65, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x75, 0x6d, 0x70, 0x5f,
+     0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18,
+     0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x75, 0x6d, 0x70, 0x49,
+     0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x2a, 0xbf, 0x06,
+     0x0a, 0x0f, 0x4d, 0x65, 0x6d, 0x69, 0x6e, 0x66, 0x6f, 0x43, 0x6f, 0x75,
+     0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+     0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x5f, 0x54, 0x4f,
+     0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x5f, 0x46, 0x52, 0x45,
+     0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x4d, 0x45, 0x4d, 0x5f, 0x41, 0x56, 0x41, 0x49, 0x4c,
+     0x41, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x42, 0x55, 0x46, 0x46, 0x45, 0x52,
+     0x53, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x44, 0x10, 0x05, 0x12,
+     0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53,
+     0x57, 0x41, 0x50, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x44, 0x10, 0x06,
+     0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
+     0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x07, 0x12, 0x14, 0x0a, 0x10,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43,
+     0x54, 0x49, 0x56, 0x45, 0x10, 0x08, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45,
+     0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x19, 0x0a, 0x15, 0x4d,
+     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54,
+     0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x0a, 0x12, 0x17,
+     0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x43,
+     0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x0b, 0x12,
+     0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x49,
+     0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45,
+     0x10, 0x0c, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46,
+     0x4f, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c,
+     0x45, 0x10, 0x0d, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x4d, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x0e,
+     0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
+     0x53, 0x57, 0x41, 0x50, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10, 0x0f,
+     0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f,
+     0x53, 0x57, 0x41, 0x50, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x10, 0x10, 0x12,
+     0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x44,
+     0x49, 0x52, 0x54, 0x59, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x42,
+     0x41, 0x43, 0x4b, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x5f, 0x50, 0x41,
+     0x47, 0x45, 0x53, 0x10, 0x13, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x4d, 0x41, 0x50, 0x50, 0x45, 0x44, 0x10,
+     0x14, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
+     0x5f, 0x53, 0x48, 0x4d, 0x45, 0x4d, 0x10, 0x15, 0x12, 0x10, 0x0a, 0x0c,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42,
+     0x10, 0x16, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46,
+     0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41,
+     0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x17, 0x12, 0x1e, 0x0a, 0x1a,
+     0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x53, 0x4c, 0x41, 0x42,
+     0x5f, 0x55, 0x4e, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42,
+     0x4c, 0x45, 0x10, 0x18, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x4d, 0x49,
+     0x4e, 0x46, 0x4f, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f, 0x53,
+     0x54, 0x41, 0x43, 0x4b, 0x10, 0x19, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x54,
+     0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, 0x1a, 0x12, 0x18, 0x0a, 0x14, 0x4d,
+     0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49,
+     0x54, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x1b, 0x12, 0x17, 0x0a,
+     0x13, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4f, 0x4d,
+     0x4d, 0x49, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x53, 0x10, 0x1c, 0x12, 0x19,
+     0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x56, 0x4d,
+     0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x10,
+     0x1d, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x4d, 0x49, 0x4e, 0x46, 0x4f,
+     0x5f, 0x56, 0x4d, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x55, 0x53, 0x45,
+     0x44, 0x10, 0x1e, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x4d, 0x49, 0x4e,
+     0x46, 0x4f, 0x5f, 0x56, 0x4d, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x43,
+     0x48, 0x55, 0x4e, 0x4b, 0x10, 0x1f, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45,
+     0x4d, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4d, 0x41, 0x5f, 0x54, 0x4f,
+     0x54, 0x41, 0x4c, 0x10, 0x20, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x4d,
+     0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x43, 0x4d, 0x41, 0x5f, 0x46, 0x52, 0x45,
+     0x45, 0x10, 0x21, 0x2a, 0xff, 0x14, 0x0a, 0x0e, 0x56, 0x6d, 0x73, 0x74,
+     0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x16,
+     0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x53,
+     0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18,
+     0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
+     0x46, 0x52, 0x45, 0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x01,
+     0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e,
+     0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x42, 0x41, 0x54, 0x43,
+     0x48, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56,
+     0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x43,
+     0x54, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x04, 0x12,
+     0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x46, 0x49,
+     0x4c, 0x45, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45,
+     0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x55, 0x4e, 0x45,
+     0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x07, 0x12, 0x13,
+     0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f,
+     0x4d, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x41, 0x4e, 0x4f,
+     0x4e, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x09, 0x12, 0x14, 0x0a,
+     0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4d,
+     0x41, 0x50, 0x50, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x49, 0x4c,
+     0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x0b, 0x12, 0x13, 0x0a,
+     0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44,
+     0x49, 0x52, 0x54, 0x59, 0x10, 0x0c, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49, 0x54,
+     0x45, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x0d, 0x12, 0x1e, 0x0a, 0x1a, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x4c, 0x41,
+     0x42, 0x5f, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c,
+     0x45, 0x10, 0x0e, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x5f, 0x55, 0x4e,
+     0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x41, 0x42, 0x4c, 0x45, 0x10,
+     0x0f, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x41, 0x42, 0x4c,
+     0x45, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x10, 0x12, 0x1a, 0x0a,
+     0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x4b,
+     0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x43, 0x4b, 0x10,
+     0x11, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x48, 0x45, 0x41, 0x44, 0x10,
+     0x12, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10,
+     0x13, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x42, 0x4f, 0x55, 0x4e, 0x43, 0x45, 0x10, 0x14, 0x12,
+     0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x56, 0x4d, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x57, 0x52, 0x49, 0x54,
+     0x45, 0x10, 0x15, 0x12, 0x26, 0x0a, 0x22, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x56, 0x4d, 0x53, 0x43, 0x41, 0x4e, 0x5f,
+     0x49, 0x4d, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45,
+     0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x16, 0x12, 0x1c, 0x0a, 0x18, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49,
+     0x54, 0x45, 0x42, 0x41, 0x43, 0x4b, 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x10,
+     0x17, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x49, 0x53, 0x4f, 0x4c, 0x41, 0x54, 0x45, 0x44, 0x5f,
+     0x41, 0x4e, 0x4f, 0x4e, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x49, 0x53, 0x4f, 0x4c,
+     0x41, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x19, 0x12,
+     0x13, 0x0a, 0x0f, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52,
+     0x5f, 0x53, 0x48, 0x4d, 0x45, 0x4d, 0x10, 0x1a, 0x12, 0x15, 0x0a, 0x11,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49,
+     0x52, 0x54, 0x49, 0x45, 0x44, 0x10, 0x1b, 0x12, 0x15, 0x0a, 0x11, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x57, 0x52, 0x49,
+     0x54, 0x54, 0x45, 0x4e, 0x10, 0x1c, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x50, 0x41, 0x47, 0x45,
+     0x53, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x1d, 0x12,
+     0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f,
+     0x52, 0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x46,
+     0x41, 0x55, 0x4c, 0x54, 0x10, 0x1e, 0x12, 0x1e, 0x0a, 0x1a, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47,
+     0x53, 0x45, 0x54, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45,
+     0x10, 0x1f, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x49, 0x4e, 0x47, 0x53, 0x45, 0x54, 0x5f,
+     0x4e, 0x4f, 0x44, 0x45, 0x52, 0x45, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10,
+     0x20, 0x12, 0x28, 0x0a, 0x24, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x4e, 0x52, 0x5f, 0x41, 0x4e, 0x4f, 0x4e, 0x5f, 0x54, 0x52, 0x41, 0x4e,
+     0x53, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54, 0x5f, 0x48, 0x55, 0x47, 0x45,
+     0x50, 0x41, 0x47, 0x45, 0x53, 0x10, 0x21, 0x12, 0x16, 0x0a, 0x12, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x46, 0x52, 0x45,
+     0x45, 0x5f, 0x43, 0x4d, 0x41, 0x10, 0x22, 0x12, 0x17, 0x0a, 0x13, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x53, 0x57, 0x41,
+     0x50, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10, 0x23, 0x12, 0x1d, 0x0a, 0x19,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49,
+     0x52, 0x54, 0x59, 0x5f, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c,
+     0x44, 0x10, 0x24, 0x12, 0x28, 0x0a, 0x24, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x4e, 0x52, 0x5f, 0x44, 0x49, 0x52, 0x54, 0x59, 0x5f, 0x42,
+     0x41, 0x43, 0x4b, 0x47, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x54, 0x48,
+     0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x10, 0x25, 0x12, 0x11, 0x0a,
+     0x0d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47,
+     0x49, 0x4e, 0x10, 0x26, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x50, 0x47, 0x4f, 0x55, 0x54, 0x10, 0x27,
+     0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50,
+     0x47, 0x50, 0x47, 0x4f, 0x55, 0x54, 0x43, 0x4c, 0x45, 0x41, 0x4e, 0x10,
+     0x28, 0x12, 0x11, 0x0a, 0x0d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x53, 0x57, 0x50, 0x49, 0x4e, 0x10, 0x29, 0x12, 0x12, 0x0a, 0x0e,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x53, 0x57, 0x50, 0x4f,
+     0x55, 0x54, 0x10, 0x2a, 0x12, 0x16, 0x0a, 0x12, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x44,
+     0x4d, 0x41, 0x10, 0x2b, 0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x5f, 0x4e,
+     0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x2c, 0x12, 0x1a, 0x0a, 0x16, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x4c, 0x4c, 0x4f,
+     0x43, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x2d, 0x12,
+     0x11, 0x0a, 0x0d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x46, 0x52, 0x45, 0x45, 0x10, 0x2e, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x41, 0x43, 0x54, 0x49, 0x56,
+     0x41, 0x54, 0x45, 0x10, 0x2f, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x44, 0x45, 0x41, 0x43, 0x54, 0x49,
+     0x56, 0x41, 0x54, 0x45, 0x10, 0x30, 0x12, 0x12, 0x0a, 0x0e, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x46, 0x41, 0x55, 0x4c, 0x54,
+     0x10, 0x31, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x4d, 0x41, 0x4a, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10,
+     0x32, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c, 0x4c, 0x5f, 0x44, 0x4d, 0x41,
+     0x10, 0x33, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c, 0x4c, 0x5f, 0x4e, 0x4f,
+     0x52, 0x4d, 0x41, 0x4c, 0x10, 0x34, 0x12, 0x1b, 0x0a, 0x17, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x52, 0x45, 0x46, 0x49, 0x4c,
+     0x4c, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x35, 0x12,
+     0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44,
+     0x5f, 0x44, 0x4d, 0x41, 0x10, 0x36, 0x12, 0x20, 0x0a, 0x1c, 0x56, 0x4d,
+     0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c,
+     0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4e, 0x4f, 0x52, 0x4d,
+     0x41, 0x4c, 0x10, 0x37, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x4b,
+     0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c,
+     0x45, 0x10, 0x38, 0x12, 0x1d, 0x0a, 0x19, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49,
+     0x52, 0x45, 0x43, 0x54, 0x5f, 0x44, 0x4d, 0x41, 0x10, 0x39, 0x12, 0x20,
+     0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53,
+     0x54, 0x45, 0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f,
+     0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x3a, 0x12, 0x21, 0x0a, 0x1d,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x54, 0x45,
+     0x41, 0x4c, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x4f,
+     0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x3b, 0x12, 0x1c, 0x0a, 0x18, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e,
+     0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x44, 0x4d, 0x41, 0x10,
+     0x3c, 0x12, 0x1f, 0x0a, 0x1b, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50,
+     0x44, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x3d, 0x12, 0x20,
+     0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53,
+     0x43, 0x41, 0x4e, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4d,
+     0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x3e, 0x12, 0x1c, 0x0a, 0x18,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43, 0x41,
+     0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x44, 0x4d, 0x41,
+     0x10, 0x3f, 0x12, 0x1f, 0x0a, 0x1b, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45,
+     0x43, 0x54, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x40, 0x12,
+     0x20, 0x0a, 0x1c, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47,
+     0x53, 0x43, 0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f,
+     0x4d, 0x4f, 0x56, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x41, 0x12, 0x21, 0x0a,
+     0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x53, 0x43,
+     0x41, 0x4e, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x48,
+     0x52, 0x4f, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x42, 0x12, 0x17, 0x0a, 0x13,
+     0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x49, 0x4e, 0x4f,
+     0x44, 0x45, 0x53, 0x54, 0x45, 0x41, 0x4c, 0x10, 0x43, 0x12, 0x18, 0x0a,
+     0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x53, 0x4c, 0x41, 0x42,
+     0x53, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x44, 0x12,
+     0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53,
+     0x57, 0x41, 0x50, 0x44, 0x5f, 0x49, 0x4e, 0x4f, 0x44, 0x45, 0x53, 0x54,
+     0x45, 0x41, 0x4c, 0x10, 0x45, 0x12, 0x27, 0x0a, 0x23, 0x56, 0x4d, 0x53,
+     0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57, 0x41, 0x50, 0x44, 0x5f, 0x4c,
+     0x4f, 0x57, 0x5f, 0x57, 0x4d, 0x41, 0x52, 0x4b, 0x5f, 0x48, 0x49, 0x54,
+     0x5f, 0x51, 0x55, 0x49, 0x43, 0x4b, 0x4c, 0x59, 0x10, 0x46, 0x12, 0x28,
+     0x0a, 0x24, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x4b, 0x53, 0x57,
+     0x41, 0x50, 0x44, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x5f, 0x57, 0x4d, 0x41,
+     0x52, 0x4b, 0x5f, 0x48, 0x49, 0x54, 0x5f, 0x51, 0x55, 0x49, 0x43, 0x4b,
+     0x4c, 0x59, 0x10, 0x47, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x4f, 0x55, 0x54, 0x52, 0x55,
+     0x4e, 0x10, 0x48, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x53, 0x54, 0x41, 0x4c, 0x4c,
+     0x10, 0x49, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x50, 0x47, 0x52, 0x4f, 0x54, 0x41, 0x54, 0x45, 0x44, 0x10, 0x4a,
+     0x12, 0x19, 0x0a, 0x15, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x44,
+     0x52, 0x4f, 0x50, 0x5f, 0x50, 0x41, 0x47, 0x45, 0x43, 0x41, 0x43, 0x48,
+     0x45, 0x10, 0x4b, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x53, 0x4c, 0x41, 0x42, 0x10,
+     0x4c, 0x12, 0x1c, 0x0a, 0x18, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f,
+     0x50, 0x47, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x55,
+     0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x4d, 0x12, 0x19, 0x0a, 0x15, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x50, 0x47, 0x4d, 0x49, 0x47, 0x52,
+     0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x4e, 0x12, 0x22,
+     0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d,
+     0x50, 0x41, 0x43, 0x54, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45,
+     0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x4f, 0x12, 0x1f,
+     0x0a, 0x1b, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d,
+     0x50, 0x41, 0x43, 0x54, 0x5f, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x53, 0x43,
+     0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x50, 0x12, 0x1b, 0x0a, 0x17, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43,
+     0x54, 0x5f, 0x49, 0x53, 0x4f, 0x4c, 0x41, 0x54, 0x45, 0x44, 0x10, 0x51,
+     0x12, 0x18, 0x0a, 0x14, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43,
+     0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x4c, 0x4c,
+     0x10, 0x52, 0x12, 0x17, 0x0a, 0x13, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x46, 0x41, 0x49,
+     0x4c, 0x10, 0x53, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x5f, 0x53, 0x55,
+     0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x54, 0x12, 0x1e, 0x0a, 0x1a, 0x56,
+     0x4d, 0x53, 0x54, 0x41, 0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x43,
+     0x54, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x5f, 0x57, 0x41, 0x4b,
+     0x45, 0x10, 0x55, 0x12, 0x21, 0x0a, 0x1d, 0x56, 0x4d, 0x53, 0x54, 0x41,
+     0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c,
+     0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x43, 0x55, 0x4c, 0x4c, 0x45, 0x44,
+     0x10, 0x56, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45,
+     0x5f, 0x50, 0x47, 0x53, 0x5f, 0x53, 0x43, 0x41, 0x4e, 0x4e, 0x45, 0x44,
+     0x10, 0x57, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45,
+     0x5f, 0x50, 0x47, 0x53, 0x5f, 0x52, 0x45, 0x53, 0x43, 0x55, 0x45, 0x44,
+     0x10, 0x58, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45,
+     0x5f, 0x50, 0x47, 0x53, 0x5f, 0x4d, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44,
+     0x10, 0x59, 0x12, 0x24, 0x0a, 0x20, 0x56, 0x4d, 0x53, 0x54, 0x41, 0x54,
+     0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42, 0x4c, 0x45,
+     0x5f, 0x50, 0x47, 0x53, 0x5f, 0x4d, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b,
+     0x45, 0x44, 0x10, 0x5a, 0x12, 0x22, 0x0a, 0x1e, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42,
+     0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52,
+     0x45, 0x44, 0x10, 0x5b, 0x12, 0x23, 0x0a, 0x1f, 0x56, 0x4d, 0x53, 0x54,
+     0x41, 0x54, 0x5f, 0x55, 0x4e, 0x45, 0x56, 0x49, 0x43, 0x54, 0x41, 0x42,
+     0x4c, 0x45, 0x5f, 0x50, 0x47, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x4e,
+     0x44, 0x45, 0x44, 0x10, 0x5c}};
+
+}  // namespace perfetto
+
+#endif  // SRC_PERFETTO_CMD_PERFETTO_CONFIG_DESCRIPTOR_H_
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index 44fbf2f..8fc7552 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -45,10 +45,14 @@
     "../../../gn:default_deps",
     "../../base",
     "../../base:unix_socket",
+    "../../tracing",
+    "../../tracing:ipc",
   ]
   sources = [
     "bookkeeping.cc",
     "bookkeeping.h",
+    "heapprofd_producer.cc",
+    "heapprofd_producer.h",
     "queue_messages.h",
     "record_reader.cc",
     "record_reader.h",
diff --git a/src/profiling/memory/bookkeeping.cc b/src/profiling/memory/bookkeeping.cc
index 5b8c280..51e0d30 100644
--- a/src/profiling/memory/bookkeeping.cc
+++ b/src/profiling/memory/bookkeeping.cc
@@ -173,9 +173,16 @@
   }
 
   if (rec->record_type == BookkeepingRecord::Type::Dump) {
+    DumpRecord& dump_rec = rec->dump_record;
+    std::shared_ptr<TraceWriter> trace_writer = dump_rec.trace_writer.lock();
+    if (!trace_writer)
+      return;
     PERFETTO_LOG("Dumping heaps");
-    auto it = bookkeeping_data_.begin();
-    while (it != bookkeeping_data_.end()) {
+    for (const pid_t pid : dump_rec.pids) {
+      auto it = bookkeeping_data_.find(pid);
+      if (it == bookkeeping_data_.end())
+        continue;
+
       std::string dump_file_name = file_name_ + "." + std::to_string(it->first);
       PERFETTO_LOG("Dumping %d to %s", it->first, dump_file_name.c_str());
       base::ScopedFile fd =
@@ -188,10 +195,9 @@
       if (it->second.ref_count == 0) {
         std::lock_guard<std::mutex> l(bookkeeping_mutex_);
         it = bookkeeping_data_.erase(it);
-      } else {
-        ++it;
       }
     }
+    dump_rec.callback();
   } else if (rec->record_type == BookkeepingRecord::Type::Free) {
     FreeRecord& free_rec = rec->free_record;
     FreePageEntry* entries = free_rec.metadata->entries;
diff --git a/src/profiling/memory/bounded_queue_unittest.cc b/src/profiling/memory/bounded_queue_unittest.cc
index 738180d..5ffa0d9 100644
--- a/src/profiling/memory/bounded_queue_unittest.cc
+++ b/src/profiling/memory/bounded_queue_unittest.cc
@@ -33,6 +33,7 @@
   EXPECT_EQ(out, 1);
   EXPECT_TRUE(q.Get(&out));
   EXPECT_EQ(out, 2);
+  q.Shutdown();
 }
 
 TEST(BoundedQueueTest, BlockingAdd) {
@@ -48,6 +49,7 @@
   EXPECT_TRUE(q.Get(&out));
   EXPECT_EQ(out, 3);
   th.join();
+  q.Shutdown();
 }
 
 TEST(BoundedQueueTest, BlockingGet) {
@@ -59,6 +61,7 @@
   });
   q.Add(1);
   th.join();
+  q.Shutdown();
 }
 
 TEST(BoundedQueueTest, Resize) {
@@ -74,6 +77,7 @@
   EXPECT_EQ(out, 2);
   EXPECT_TRUE(q.Get(&out));
   EXPECT_EQ(out, 3);
+  q.Shutdown();
 }
 
 TEST(BoundedQueueTest, Shutdown) {
@@ -95,11 +99,6 @@
   q.Add(1);
   q.Add(2);
   std::thread th([&q] { EXPECT_FALSE(q.Add(3)); });
-  int out;
-  EXPECT_TRUE(q.Get(&out));
-  EXPECT_EQ(out, 1);
-  EXPECT_TRUE(q.Get(&out));
-  EXPECT_EQ(out, 2);
   q.Shutdown();
   th.join();
 }
diff --git a/src/profiling/memory/heapprofd_integrationtest.cc b/src/profiling/memory/heapprofd_integrationtest.cc
index 5850d1f..44e4292 100644
--- a/src/profiling/memory/heapprofd_integrationtest.cc
+++ b/src/profiling/memory/heapprofd_integrationtest.cc
@@ -59,7 +59,6 @@
   auto done = task_runner.CreateCheckpoint("done");
   constexpr uint64_t kSamplingInterval = 123;
   SocketListener listener(
-      {kSamplingInterval},
       [&done, &bookkeeping_thread](UnwindingRecord r) {
         // TODO(fmayer): Test symbolization and result of unwinding.
         // This check will only work on in-tree builds as out-of-tree
@@ -71,6 +70,7 @@
       },
       &bookkeeping_thread);
 
+  listener.ExpectPID(getpid(), {kSamplingInterval});
   auto sock = base::UnixSocket::Listen(kSocketName, &listener, &task_runner);
   if (!sock->is_listening()) {
     PERFETTO_ELOG("Socket not listening.");
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
new file mode 100644
index 0000000..912c8c1
--- /dev/null
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/memory/heapprofd_producer.h"
+
+#include <inttypes.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#include "perfetto/tracing/core/data_source_config.h"
+#include "perfetto/tracing/core/data_source_descriptor.h"
+#include "perfetto/tracing/core/trace_writer.h"
+#include "perfetto/tracing/ipc/producer_ipc_client.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+constexpr char kHeapprofdDataSource[] = "android.heapprofd";
+constexpr size_t kUnwinderQueueSize = 1000;
+constexpr size_t kBookkeepingQueueSize = 1000;
+constexpr size_t kUnwinderThreads = 5;
+constexpr const char* kDumpOutput = "/data/misc/perfetto-traces/heap_dump";
+constexpr int kHeapprofdSignal = 36;
+
+constexpr uint32_t kInitialConnectionBackoffMs = 100;
+constexpr uint32_t kMaxConnectionBackoffMs = 30 * 1000;
+
+ClientConfiguration MakeClientConfiguration(const DataSourceConfig& cfg) {
+  ClientConfiguration client_config;
+  client_config.interval = cfg.heapprofd_config().sampling_interval_bytes();
+  return client_config;
+}
+
+void FindPidsForBinaries(std::vector<std::string> binaries,
+                         std::vector<pid_t>* pids) {
+  base::ScopedDir proc_dir(opendir("/proc"));
+  if (!proc_dir) {
+    PERFETTO_DFATAL("Failed to open /proc");
+    return;
+  }
+  struct dirent* entry;
+  while ((entry = readdir(*proc_dir))) {
+    char* end;
+    long int pid = strtol(entry->d_name, &end, 10);
+    if (*end != '\0') {
+      continue;
+    }
+
+    char link_buf[128];
+    char binary_buf[128];
+
+    if (snprintf(link_buf, sizeof(link_buf), "/proc/%lu/exe", pid) < 0) {
+      PERFETTO_DFATAL("Failed to create exe filename for %lu", pid);
+      continue;
+    }
+    ssize_t link_size = readlink(link_buf, binary_buf, sizeof(binary_buf));
+    if (link_size < 0) {
+      continue;
+    }
+    if (link_size == sizeof(binary_buf)) {
+      PERFETTO_DFATAL("Potential overflow in binary name.");
+      continue;
+    }
+    binary_buf[link_size] = '\0';
+    for (const std::string& binary : binaries) {
+      if (binary == binary_buf)
+        pids->emplace_back(static_cast<pid_t>(pid));
+    }
+  }
+}
+
+}  // namespace
+
+// We create kUnwinderThreads unwinding threads and one bookeeping thread.
+// The bookkeeping thread is singleton in order to avoid expensive and
+// complicated synchronisation in the bookkeeping.
+//
+// We wire up the system by creating BoundedQueues between the threads. The main
+// thread runs the TaskRunner driving the SocketListener. The unwinding thread
+// takes the data received by the SocketListener and if it is a malloc does
+// stack unwinding, and if it is a free just forwards the content of the record
+// to the bookkeeping thread.
+//
+//             +--------------+
+//             |SocketListener|
+//             +------+-------+
+//                    |
+//          +--UnwindingRecord -+
+//          |                   |
+// +--------v-------+   +-------v--------+
+// |Unwinding Thread|   |Unwinding Thread|
+// +--------+-------+   +-------+--------+
+//          |                   |
+//          +-BookkeepingRecord +
+//                    |
+//           +--------v---------+
+//           |Bookkeeping Thread|
+//           +------------------+
+
+HeapprofdProducer::HeapprofdProducer(base::TaskRunner* task_runner)
+    : task_runner_(task_runner),
+      bookkeeping_queue_(kBookkeepingQueueSize),
+      bookkeeping_thread_(kDumpOutput),
+      bookkeeping_th_([this] { bookkeeping_thread_.Run(&bookkeeping_queue_); }),
+      unwinder_queues_(MakeUnwinderQueues(kUnwinderThreads)),
+      unwinding_threads_(MakeUnwindingThreads(kUnwinderThreads)),
+      socket_listener_(MakeSocketListenerCallback(), &bookkeeping_thread_),
+      socket_(MakeSocket()),
+      weak_factory_(this) {}
+
+HeapprofdProducer::~HeapprofdProducer() {
+  bookkeeping_queue_.Shutdown();
+  for (auto& queue : unwinder_queues_) {
+    queue.Shutdown();
+  }
+  bookkeeping_th_.join();
+  for (std::thread& th : unwinding_threads_) {
+    th.join();
+  }
+}
+
+void HeapprofdProducer::OnConnect() {
+  // TODO(fmayer): Delete once we have generic reconnect logic.
+  PERFETTO_DCHECK(state_ == kConnecting);
+  state_ = kConnected;
+  ResetConnectionBackoff();
+  PERFETTO_LOG("Connected to the service");
+
+  DataSourceDescriptor desc;
+  desc.set_name(kHeapprofdDataSource);
+  endpoint_->RegisterDataSource(desc);
+}
+
+// TODO(fmayer): Delete once we have generic reconnect logic.
+void HeapprofdProducer::OnDisconnect() {
+  PERFETTO_DCHECK(state_ == kConnected || state_ == kConnecting);
+  PERFETTO_LOG("Disconnected from tracing service");
+  if (state_ == kConnected)
+    return task_runner_->PostTask([this] { this->Restart(); });
+
+  state_ = kNotConnected;
+  IncreaseConnectionBackoff();
+  task_runner_->PostDelayedTask([this] { this->Connect(); },
+                                connection_backoff_ms_);
+}
+
+void HeapprofdProducer::SetupDataSource(DataSourceInstanceID id,
+                                        const DataSourceConfig& cfg) {
+  PERFETTO_DLOG("Setting up data source.");
+  if (cfg.name() != kHeapprofdDataSource) {
+    PERFETTO_DLOG("Invalid data source name.");
+    return;
+  }
+
+  auto it = data_sources_.find(id);
+  if (it != data_sources_.end()) {
+    PERFETTO_DFATAL("Received duplicated data source instance id: %" PRIu64,
+                    id);
+    return;
+  }
+
+  DataSource data_source;
+
+  ClientConfiguration client_config = MakeClientConfiguration(cfg);
+
+  for (uint64_t pid : cfg.heapprofd_config().pid())
+    data_source.pids.emplace_back(static_cast<pid_t>(pid));
+
+  FindPidsForBinaries(cfg.heapprofd_config().native_binary_name(),
+                      &data_source.pids);
+
+  auto pid_it = data_source.pids.begin();
+  while (pid_it != data_source.pids.end()) {
+    auto profiling_session = socket_listener_.ExpectPID(*pid_it, client_config);
+    if (!profiling_session) {
+      PERFETTO_DFATAL("No enabling duplicate profiling session for %d",
+                      *pid_it);
+      pid_it = data_source.pids.erase(pid_it);
+      continue;
+    }
+    data_source.sessions.emplace_back(std::move(profiling_session));
+    ++pid_it;
+  }
+
+  if (data_source.pids.empty()) {
+    // TODO(fmayer): Whole system profiling.
+    PERFETTO_DLOG("No valid pids given. Not setting up data source.");
+    return;
+  }
+
+  auto buffer_id = static_cast<BufferID>(cfg.target_buffer());
+  data_source.trace_writer = endpoint_->CreateTraceWriter(buffer_id);
+
+  data_sources_.emplace(id, std::move(data_source));
+  PERFETTO_DLOG("Set up data source.");
+}
+
+void HeapprofdProducer::DoContinuousDump(DataSourceInstanceID id,
+                                         uint32_t dump_interval) {
+  if (!Dump(id, 0 /* flush_id */, false /* is_flush */))
+    return;
+  auto weak_producer = weak_factory_.GetWeakPtr();
+  task_runner_->PostDelayedTask(
+      [weak_producer, id, dump_interval] {
+        if (!weak_producer)
+          return;
+        weak_producer->DoContinuousDump(id, dump_interval);
+      },
+      dump_interval);
+}
+
+void HeapprofdProducer::StartDataSource(DataSourceInstanceID id,
+                                        const DataSourceConfig& cfg) {
+  PERFETTO_DLOG("Start DataSource");
+  auto it = data_sources_.find(id);
+  if (it == data_sources_.end()) {
+    PERFETTO_DFATAL("Received invalid data source instance to start: %" PRIu64,
+                    id);
+    return;
+  }
+
+  const DataSource& data_source = it->second;
+
+  for (pid_t pid : data_source.pids) {
+    PERFETTO_DLOG("Sending %d to %d", kHeapprofdSignal, pid);
+    if (kill(pid, kHeapprofdSignal) != 0) {
+      PERFETTO_DPLOG("kill");
+    }
+  }
+
+  const auto continuous_dump_config =
+      cfg.heapprofd_config().continuous_dump_config();
+  uint32_t dump_interval = continuous_dump_config.dump_interval_ms();
+  if (dump_interval) {
+    auto weak_producer = weak_factory_.GetWeakPtr();
+    task_runner_->PostDelayedTask(
+        [weak_producer, id, dump_interval] {
+          if (!weak_producer)
+            return;
+          weak_producer->DoContinuousDump(id, dump_interval);
+        },
+        continuous_dump_config.dump_phase_ms());
+  }
+  PERFETTO_DLOG("Started DataSource");
+}
+
+void HeapprofdProducer::StopDataSource(DataSourceInstanceID id) {
+  // DataSource holds ProfilingSession handles which on being destructed tear
+  // down the profiling on the client.
+  if (data_sources_.erase(id) != 1)
+    PERFETTO_DFATAL("Trying to stop non existing data source: %" PRIu64, id);
+}
+
+void HeapprofdProducer::OnTracingSetup() {}
+
+bool HeapprofdProducer::Dump(DataSourceInstanceID id,
+                             FlushRequestID flush_id,
+                             bool has_flush_id) {
+  PERFETTO_DLOG("Dumping %" PRIu64 ", flush: %d", id, has_flush_id);
+  auto it = data_sources_.find(id);
+  if (it == data_sources_.end()) {
+    return false;
+  }
+
+  const DataSource& data_source = it->second;
+  BookkeepingRecord record{};
+  record.record_type = BookkeepingRecord::Type::Dump;
+  DumpRecord& dump_record = record.dump_record;
+  dump_record.pids = data_source.pids;
+  dump_record.trace_writer = data_source.trace_writer;
+
+  auto weak_producer = weak_factory_.GetWeakPtr();
+  base::TaskRunner* task_runner = task_runner_;
+  if (has_flush_id) {
+    dump_record.callback = [task_runner, weak_producer, flush_id] {
+      task_runner->PostTask([weak_producer, flush_id] {
+        if (!weak_producer)
+          return weak_producer->FinishDataSourceFlush(flush_id);
+      });
+    };
+  } else {
+    dump_record.callback = [] {};
+  }
+
+  bookkeeping_queue_.Add(std::move(record));
+  return true;
+}
+
+void HeapprofdProducer::Flush(FlushRequestID flush_id,
+                              const DataSourceInstanceID* ids,
+                              size_t num_ids) {
+  if (num_ids == 0)
+    return;
+
+  size_t& flush_in_progress = flushes_in_progress_[flush_id];
+  PERFETTO_DCHECK(flush_in_progress == 0);
+  flush_in_progress = num_ids;
+  for (size_t i = 0; i < num_ids; ++i)
+    Dump(ids[i], flush_id, true);
+}
+
+void HeapprofdProducer::FinishDataSourceFlush(FlushRequestID flush_id) {
+  auto it = flushes_in_progress_.find(flush_id);
+  if (it == flushes_in_progress_.end()) {
+    PERFETTO_DFATAL("FinishDataSourceFlush id invalid: %" PRIu64, flush_id);
+    return;
+  }
+  size_t& flush_in_progress = it->second;
+  if (--flush_in_progress == 0) {
+    endpoint_->NotifyFlushComplete(flush_id);
+    flushes_in_progress_.erase(flush_id);
+  }
+}
+
+std::function<void(UnwindingRecord)>
+HeapprofdProducer::MakeSocketListenerCallback() {
+  return [this](UnwindingRecord record) {
+    unwinder_queues_[static_cast<size_t>(record.pid) % kUnwinderThreads].Add(
+        std::move(record));
+  };
+}
+
+std::vector<BoundedQueue<UnwindingRecord>>
+HeapprofdProducer::MakeUnwinderQueues(size_t n) {
+  std::vector<BoundedQueue<UnwindingRecord>> ret(n);
+  for (size_t i = 0; i < n; ++i)
+    ret[i].SetCapacity(kUnwinderQueueSize);
+  return ret;
+}
+
+std::vector<std::thread> HeapprofdProducer::MakeUnwindingThreads(size_t n) {
+  std::vector<std::thread> ret;
+  for (size_t i = 0; i < n; ++i) {
+    ret.emplace_back([this, i] {
+      UnwindingMainLoop(&unwinder_queues_[i], &bookkeeping_queue_);
+    });
+  }
+  return ret;
+}
+
+std::unique_ptr<base::UnixSocket> HeapprofdProducer::MakeSocket() {
+  const char* sock_fd = getenv(kHeapprofdSocketEnvVar);
+  if (sock_fd == nullptr) {
+    unlink(kHeapprofdSocketFile);
+    return base::UnixSocket::Listen(kHeapprofdSocketFile, &socket_listener_,
+                                    task_runner_);
+  }
+  char* end;
+  int raw_fd = static_cast<int>(strtol(sock_fd, &end, 10));
+  if (*end != '\0')
+    PERFETTO_FATAL("Invalid %s. Expected decimal integer.",
+                   kHeapprofdSocketEnvVar);
+  return base::UnixSocket::Listen(base::ScopedFile(raw_fd), &socket_listener_,
+                                  task_runner_);
+}
+
+// TODO(fmayer): Delete these and used ReconnectingProducer once submitted
+void HeapprofdProducer::Restart() {
+  // We lost the connection with the tracing service. At this point we need
+  // to reset all the data sources. Trying to handle that manually is going to
+  // be error prone. What we do here is simply desroying the instance and
+  // recreating it again.
+  // TODO(hjd): Add e2e test for this.
+
+  base::TaskRunner* task_runner = task_runner_;
+  const char* socket_name = socket_name_;
+
+  // Invoke destructor and then the constructor again.
+  this->~HeapprofdProducer();
+  new (this) HeapprofdProducer(task_runner);
+
+  ConnectWithRetries(socket_name);
+}
+
+void HeapprofdProducer::ConnectWithRetries(const char* socket_name) {
+  PERFETTO_DCHECK(state_ == kNotStarted);
+  state_ = kNotConnected;
+
+  ResetConnectionBackoff();
+  socket_name_ = socket_name;
+  Connect();
+}
+
+void HeapprofdProducer::Connect() {
+  PERFETTO_DCHECK(state_ == kNotConnected);
+  state_ = kConnecting;
+  endpoint_ = ProducerIPCClient::Connect(socket_name_, this,
+                                         "android.heapprofd", task_runner_);
+}
+
+void HeapprofdProducer::IncreaseConnectionBackoff() {
+  connection_backoff_ms_ *= 2;
+  if (connection_backoff_ms_ > kMaxConnectionBackoffMs)
+    connection_backoff_ms_ = kMaxConnectionBackoffMs;
+}
+
+void HeapprofdProducer::ResetConnectionBackoff() {
+  connection_backoff_ms_ = kInitialConnectionBackoffMs;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/heapprofd_producer.h b/src/profiling/memory/heapprofd_producer.h
new file mode 100644
index 0000000..f1dd639
--- /dev/null
+++ b/src/profiling/memory/heapprofd_producer.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_MEMORY_HEAPPROFD_PRODUCER_H_
+#define SRC_PROFILING_MEMORY_HEAPPROFD_PRODUCER_H_
+
+#include <map>
+
+#include "perfetto/base/task_runner.h"
+#include "perfetto/tracing/core/basic_types.h"
+#include "perfetto/tracing/core/producer.h"
+#include "perfetto/tracing/core/tracing_service.h"
+#include "src/profiling/memory/bounded_queue.h"
+#include "src/profiling/memory/socket_listener.h"
+
+namespace perfetto {
+namespace profiling {
+
+class HeapprofdProducer : public Producer {
+ public:
+  HeapprofdProducer(base::TaskRunner* task_runner);
+  ~HeapprofdProducer() override;
+
+  // Producer Impl:
+  void OnConnect() override;
+  void OnDisconnect() override;
+  void SetupDataSource(DataSourceInstanceID, const DataSourceConfig&) override;
+  void StartDataSource(DataSourceInstanceID, const DataSourceConfig&) override;
+  void StopDataSource(DataSourceInstanceID) override;
+  void OnTracingSetup() override;
+  void Flush(FlushRequestID,
+             const DataSourceInstanceID* data_source_ids,
+             size_t num_data_sources) override;
+
+  // TODO(fmayer): Delete once we have generic reconnect logic.
+  void ConnectWithRetries(const char* socket_name);
+
+ private:
+  // TODO(fmayer): Delete once we have generic reconnect logic.
+  enum State {
+    kNotStarted = 0,
+    kNotConnected,
+    kConnecting,
+    kConnected,
+  };
+  void Connect();
+  void Restart();
+  void ResetConnectionBackoff();
+  void IncreaseConnectionBackoff();
+
+  // TODO(fmayer): Delete once we have generic reconnect logic.
+  State state_ = kNotStarted;
+  uint32_t connection_backoff_ms_ = 0;
+  const char* socket_name_ = nullptr;
+
+  std::function<void(UnwindingRecord)> MakeSocketListenerCallback();
+  std::vector<BoundedQueue<UnwindingRecord>> MakeUnwinderQueues(size_t n);
+  std::vector<std::thread> MakeUnwindingThreads(size_t n);
+  std::unique_ptr<base::UnixSocket> MakeSocket();
+
+  void FinishDataSourceFlush(FlushRequestID flush_id);
+  bool Dump(DataSourceInstanceID id,
+            FlushRequestID flush_id,
+            bool has_flush_id);
+  void DoContinuousDump(DataSourceInstanceID id, uint32_t dump_interval);
+
+  struct DataSource {
+    std::vector<pid_t> pids;
+    // This is a shared ptr so we can lend a weak_ptr to the bookkeeping
+    // thread for unwinding.
+    std::shared_ptr<TraceWriter> trace_writer;
+    // These are opaque handles that shut down the sockets in SocketListener
+    // once they go away.
+    std::vector<SocketListener::ProfilingSession> sessions;
+  };
+
+  std::map<DataSourceInstanceID, DataSource> data_sources_;
+  std::map<FlushRequestID, size_t> flushes_in_progress_;
+
+  // These two are borrowed from the caller.
+  base::TaskRunner* const task_runner_;
+  std::unique_ptr<TracingService::ProducerEndpoint> endpoint_;
+
+  BoundedQueue<BookkeepingRecord> bookkeeping_queue_;
+  BookkeepingThread bookkeeping_thread_;
+  std::thread bookkeeping_th_;
+  std::vector<BoundedQueue<UnwindingRecord>> unwinder_queues_;
+  std::vector<std::thread> unwinding_threads_;
+  SocketListener socket_listener_;
+  std::unique_ptr<base::UnixSocket> socket_;
+
+  base::WeakPtrFactory<HeapprofdProducer> weak_factory_;
+};
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_HEAPPROFD_PRODUCER_H_
diff --git a/src/profiling/memory/main.cc b/src/profiling/memory/main.cc
index 2f5c487..5648095 100644
--- a/src/profiling/memory/main.cc
+++ b/src/profiling/memory/main.cc
@@ -25,8 +25,10 @@
 #include "perfetto/base/event.h"
 #include "perfetto/base/unix_socket.h"
 #include "src/profiling/memory/bounded_queue.h"
+#include "src/profiling/memory/heapprofd_producer.h"
 #include "src/profiling/memory/socket_listener.h"
 #include "src/profiling/memory/wire_protocol.h"
+#include "src/tracing/ipc/default_socket.h"
 
 #include "perfetto/base/unix_task_runner.h"
 
@@ -34,139 +36,10 @@
 namespace profiling {
 namespace {
 
-constexpr size_t kUnwinderQueueSize = 1000;
-constexpr size_t kBookkeepingQueueSize = 1000;
-constexpr size_t kUnwinderThreads = 5;
-constexpr uint64_t kDefaultSamplingInterval = 1;
-
-base::Event* g_dump_evt = nullptr;
-
-void DumpSignalHandler(int) {
-  g_dump_evt->Notify();
-}
-
-// We create kUnwinderThreads unwinding threads and one bookeeping thread.
-// The bookkeeping thread is singleton in order to avoid expensive and
-// complicated synchronisation in the bookkeeping.
-//
-// We wire up the system by creating BoundedQueues between the threads. The main
-// thread runs the TaskRunner driving the SocketListener. The unwinding thread
-// takes the data received by the SocketListener and if it is a malloc does
-// stack unwinding, and if it is a free just forwards the content of the record
-// to the bookkeeping thread.
-//
-//             +--------------+
-//             |SocketListener|
-//             +------+-------+
-//                    |
-//          +--UnwindingRecord -+
-//          |                   |
-// +--------v-------+   +-------v--------+
-// |Unwinding Thread|   |Unwinding Thread|
-// +--------+-------+   +-------+--------+
-//          |                   |
-//          +-BookkeepingRecord +
-//                    |
-//           +--------v---------+
-//           |Bookkeeping Thread|
-//           +------------------+
-int HeapprofdMain(int argc, char** argv) {
-  // TODO(fmayer): This is temporary until heapprofd is integrated with Perfetto
-  // and receives its configuration via that.
-  uint64_t sampling_interval = kDefaultSamplingInterval;
-  bool standalone = false;
-  int opt;
-  while ((opt = getopt(argc, argv, "i:s")) != -1) {
-    switch (opt) {
-      case 'i': {
-        char* end;
-        long long sampling_interval_arg = strtoll(optarg, &end, 10);
-        if (*end != '\0' || *optarg == '\0')
-          PERFETTO_FATAL("Invalid sampling interval: %s", optarg);
-        PERFETTO_CHECK(sampling_interval > 0);
-        sampling_interval = static_cast<uint64_t>(sampling_interval_arg);
-        break;
-      }
-      case 's':
-        standalone = true;
-        break;
-      default:
-        PERFETTO_FATAL("%s [-i interval] [-s]", argv[0]);
-    }
-  }
-
+int HeapprofdMain(int, char**) {
   base::UnixTaskRunner task_runner;
-  BoundedQueue<BookkeepingRecord> bookkeeping_queue(kBookkeepingQueueSize);
-  // We set this up before launching any threads, so we do not have to use a
-  // std::atomic for g_dump_evt.
-  g_dump_evt = new base::Event();
-
-  struct sigaction action = {};
-  action.sa_handler = DumpSignalHandler;
-  PERFETTO_CHECK(sigaction(SIGUSR1, &action, nullptr) == 0);
-  task_runner.AddFileDescriptorWatch(g_dump_evt->fd(), [&bookkeeping_queue] {
-    g_dump_evt->Clear();
-
-    BookkeepingRecord rec = {};
-    rec.record_type = BookkeepingRecord::Type::Dump;
-    bookkeeping_queue.Add(std::move(rec));
-  });
-
-  std::unique_ptr<base::UnixSocket> sock;
-
-  BookkeepingThread bookkeeping_thread("/data/local/tmp/heap_dump");
-  std::thread bookkeeping_th([&bookkeeping_thread, &bookkeeping_queue] {
-    bookkeeping_thread.Run(&bookkeeping_queue);
-  });
-
-  std::array<BoundedQueue<UnwindingRecord>, kUnwinderThreads> unwinder_queues;
-  for (size_t i = 0; i < kUnwinderThreads; ++i)
-    unwinder_queues[i].SetCapacity(kUnwinderQueueSize);
-  std::vector<std::thread> unwinding_threads;
-  unwinding_threads.reserve(kUnwinderThreads);
-  for (size_t i = 0; i < kUnwinderThreads; ++i) {
-    unwinding_threads.emplace_back([&unwinder_queues, &bookkeeping_queue, i] {
-      UnwindingMainLoop(&unwinder_queues[i], &bookkeeping_queue);
-    });
-  }
-
-  auto on_record_received = [&unwinder_queues](UnwindingRecord r) {
-    unwinder_queues[static_cast<size_t>(r.pid) % kUnwinderThreads].Add(
-        std::move(r));
-  };
-  SocketListener listener({sampling_interval}, std::move(on_record_received),
-                          &bookkeeping_thread);
-
-  if (optind != argc)
-    PERFETTO_FATAL("%s [-i interval] [-s]", argv[0]);
-
-  if (standalone) {
-    // Allow to be able to manually specify the socket to listen on
-    // for testing and sideloading purposes.
-    unlink(kHeapprofdSocketFile);
-    sock =
-        base::UnixSocket::Listen(kHeapprofdSocketFile, &listener, &task_runner);
-  } else {
-    // When running as a service launched by init on Android, the socket
-    // is created by init and passed to the application using an environment
-    // variable.
-    const char* sock_fd = getenv(kHeapprofdSocketEnvVar);
-    if (sock_fd == nullptr)
-      PERFETTO_FATAL("No argument given and environment variable %s is unset.",
-                     kHeapprofdSocketEnvVar);
-    char* end;
-    int raw_fd = static_cast<int>(strtol(sock_fd, &end, 10));
-    if (*end != '\0')
-      PERFETTO_FATAL("Invalid %s. Expected decimal integer.",
-                     kHeapprofdSocketEnvVar);
-    sock = base::UnixSocket::Listen(base::ScopedFile(raw_fd), &listener,
-                                    &task_runner);
-  }
-
-  if (sock->last_error() != 0)
-    PERFETTO_FATAL("Failed to initialize socket: %s",
-                   strerror(sock->last_error()));
-
+  HeapprofdProducer producer(&task_runner);
+  producer.ConnectWithRetries(GetProducerSocket());
   task_runner.Run();
   return 0;
 }
diff --git a/src/profiling/memory/queue_messages.h b/src/profiling/memory/queue_messages.h
index b37c227..b09fbdc 100644
--- a/src/profiling/memory/queue_messages.h
+++ b/src/profiling/memory/queue_messages.h
@@ -21,18 +21,20 @@
 
 #include <unwindstack/Maps.h>
 #include <unwindstack/Unwinder.h>
+
+#include "perfetto/tracing/core/trace_writer.h"
 #include "src/profiling/memory/wire_protocol.h"
 
 namespace perfetto {
 namespace profiling {
 
-struct ProcessMetadata;
+struct UnwindingMetadata;
 
 struct UnwindingRecord {
   pid_t pid;
   size_t size;
   std::unique_ptr<uint8_t[]> data;
-  std::weak_ptr<ProcessMetadata> metadata;
+  std::weak_ptr<UnwindingMetadata> metadata;
 };
 
 struct FreeRecord {
@@ -46,6 +48,12 @@
   std::vector<unwindstack::FrameData> frames;
 };
 
+struct DumpRecord {
+  std::vector<pid_t> pids;
+  std::weak_ptr<TraceWriter> trace_writer;
+  std::function<void()> callback;
+};
+
 struct BookkeepingRecord {
   enum class Type {
     Dump = 0,
@@ -57,6 +65,7 @@
   Type record_type;
   AllocRecord alloc_record;
   FreeRecord free_record;
+  DumpRecord dump_record;
 };
 
 }  // namespace profiling
diff --git a/src/profiling/memory/socket_listener.cc b/src/profiling/memory/socket_listener.cc
index 9009bb0..733c6ea 100644
--- a/src/profiling/memory/socket_listener.cc
+++ b/src/profiling/memory/socket_listener.cc
@@ -22,6 +22,13 @@
 
 void SocketListener::OnDisconnect(base::UnixSocket* self) {
   bookkeeping_thread_->NotifyClientDisconnected(self->peer_pid());
+  auto it = process_info_.find(self->peer_pid());
+  if (it != process_info_.end()) {
+    ProcessInfo& process_info = it->second;
+    process_info.sockets.erase(self);
+  } else {
+    PERFETTO_DFATAL("Disconnect from socket without ProcessInfo.");
+  }
   sockets_.erase(self);
 }
 
@@ -29,8 +36,20 @@
     base::UnixSocket*,
     std::unique_ptr<base::UnixSocket> new_connection) {
   base::UnixSocket* new_connection_raw = new_connection.get();
+  pid_t pid = new_connection_raw->peer_pid();
+
+  auto it = process_info_.find(pid);
+  if (it == process_info_.end()) {
+    PERFETTO_DFATAL("Unexpected connection.");
+    return;
+  }
+  ProcessInfo& process_info = it->second;
+
   sockets_.emplace(new_connection_raw, std::move(new_connection));
-  bookkeeping_thread_->NotifyClientConnected(new_connection_raw->peer_pid());
+  process_info.sockets.emplace(new_connection_raw);
+  // TODO(fmayer): Move destruction of bookkeeping data to
+  // HeapprofdProducer.
+  bookkeeping_thread_->NotifyClientConnected(pid);
 }
 
 void SocketListener::OnDataAvailable(base::UnixSocket* self) {
@@ -42,32 +61,42 @@
 
   Entry& entry = socket_it->second;
   RecordReader::ReceiveBuffer buf = entry.record_reader.BeginReceive();
+
+  auto process_info_it = process_info_.find(peer_pid);
+  if (process_info_it == process_info_.end()) {
+    PERFETTO_DFATAL("This should not happen.");
+    return;
+  }
+  ProcessInfo& process_info = process_info_it->second;
+
   size_t rd;
   if (PERFETTO_LIKELY(entry.recv_fds)) {
     rd = self->Receive(buf.data, buf.size);
   } else {
-    auto it = process_metadata_.find(peer_pid);
-    if (it != process_metadata_.end() && !it->second.expired()) {
+    auto it = unwinding_metadata_.find(peer_pid);
+    if (it != unwinding_metadata_.end() && !it->second.expired()) {
       entry.recv_fds = true;
       // If the process already has metadata, this is an additional socket for
       // an existing process. Reuse existing metadata and close the received
       // file descriptors.
-      entry.process_metadata = std::shared_ptr<ProcessMetadata>(it->second);
+      entry.unwinding_metadata = std::shared_ptr<UnwindingMetadata>(it->second);
       rd = self->Receive(buf.data, buf.size);
     } else {
       base::ScopedFile fds[2];
       rd = self->Receive(buf.data, buf.size, fds, base::ArraySize(fds));
       if (fds[0] && fds[1]) {
+        PERFETTO_DLOG("%d: Received FDs.", peer_pid);
         entry.recv_fds = true;
-        entry.process_metadata = std::make_shared<ProcessMetadata>(
+        entry.unwinding_metadata = std::make_shared<UnwindingMetadata>(
             peer_pid, std::move(fds[0]), std::move(fds[1]));
-        process_metadata_[peer_pid] = entry.process_metadata;
-        self->Send(&client_config_, sizeof(client_config_), -1,
+        unwinding_metadata_[peer_pid] = entry.unwinding_metadata;
+        self->Send(&process_info.client_config,
+                   sizeof(process_info.client_config), -1,
                    base::UnixSocket::BlockingMode::kBlocking);
       } else if (fds[0] || fds[1]) {
-        PERFETTO_DLOG("Received partial FDs.");
+        PERFETTO_DLOG("%d: Received partial FDs.", peer_pid);
       } else {
-        PERFETTO_DLOG("Received no FDs.");
+        PERFETTO_DLOG("%d: Received no FDs.", peer_pid);
       }
     }
   }
@@ -86,6 +115,30 @@
   }
 }
 
+SocketListener::ProfilingSession SocketListener::ExpectPID(
+    pid_t pid,
+    ClientConfiguration cfg) {
+  PERFETTO_DLOG("Expecting connection from %d", pid);
+  bool inserted;
+  std::tie(std::ignore, inserted) = process_info_.emplace(pid, std::move(cfg));
+  if (!inserted)
+    return ProfilingSession(0, nullptr);
+  return ProfilingSession(pid, this);
+}
+
+void SocketListener::ShutdownPID(pid_t pid) {
+  PERFETTO_DLOG("Shutting down connecting from %d", pid);
+  auto it = process_info_.find(pid);
+  if (it == process_info_.end()) {
+    PERFETTO_DFATAL("Shutting down nonexistant pid.");
+    return;
+  }
+  ProcessInfo& process_info = it->second;
+  // Disconnect all sockets for process.
+  for (base::UnixSocket* socket : process_info.sockets)
+    socket->Shutdown(true);
+}
+
 void SocketListener::RecordReceived(base::UnixSocket* self,
                                     size_t size,
                                     std::unique_ptr<uint8_t[]> buf) {
@@ -97,7 +150,7 @@
     return;
   }
   Entry& entry = it->second;
-  if (!entry.process_metadata) {
+  if (!entry.unwinding_metadata) {
     PERFETTO_DLOG("Received record without process metadata.");
     return;
   }
@@ -107,12 +160,12 @@
     return;
   }
   // This needs to be a weak_ptr for two reasons:
-  // 1) most importantly, the weak_ptr in process_metadata_ should expire as
+  // 1) most importantly, the weak_ptr in unwinding_metadata_ should expire as
   // soon as the last socket for a process goes away. Otherwise, a recycled
   // PID might reuse incorrect metadata.
   // 2) it is a waste to unwind for a process that had already gone away.
-  std::weak_ptr<ProcessMetadata> weak_metadata(entry.process_metadata);
-  callback_function_({entry.process_metadata->pid, size, std::move(buf),
+  std::weak_ptr<UnwindingMetadata> weak_metadata(entry.unwinding_metadata);
+  callback_function_({entry.unwinding_metadata->pid, size, std::move(buf),
                       std::move(weak_metadata)});
 }
 
diff --git a/src/profiling/memory/socket_listener.h b/src/profiling/memory/socket_listener.h
index 69738b0..8c2c3e8 100644
--- a/src/profiling/memory/socket_listener.h
+++ b/src/profiling/memory/socket_listener.h
@@ -32,11 +32,43 @@
 
 class SocketListener : public base::UnixSocket::EventListener {
  public:
-  SocketListener(ClientConfiguration client_config,
-                 std::function<void(UnwindingRecord)> fn,
+  friend class ProfilingSession;
+  class ProfilingSession {
+   public:
+    friend class SocketListener;
+
+    ProfilingSession(ProfilingSession&& other)
+        : pid_(other.pid_), listener_(other.listener_) {
+      other.listener_ = nullptr;
+    }
+
+    ~ProfilingSession() {
+      if (listener_)
+        listener_->ShutdownPID(pid_);
+    }
+    ProfilingSession& operator=(ProfilingSession&& other) {
+      pid_ = other.pid_;
+      listener_ = other.listener_;
+      other.listener_ = nullptr;
+      return *this;
+    }
+
+    operator bool() const { return listener_ != nullptr; }
+
+    ProfilingSession(const ProfilingSession&) = delete;
+    ProfilingSession& operator=(const ProfilingSession&) = delete;
+
+   private:
+    ProfilingSession(pid_t pid, SocketListener* listener)
+        : pid_(pid), listener_(listener) {}
+
+    pid_t pid_;
+    SocketListener* listener_ = nullptr;
+  };
+
+  SocketListener(std::function<void(UnwindingRecord)> fn,
                  BookkeepingThread* bookkeeping_thread)
-      : client_config_(client_config),
-        callback_function_(std::move(fn)),
+      : callback_function_(std::move(fn)),
         bookkeeping_thread_(bookkeeping_thread) {}
   void OnDisconnect(base::UnixSocket* self) override;
   void OnNewIncomingConnection(
@@ -44,7 +76,15 @@
       std::unique_ptr<base::UnixSocket> new_connection) override;
   void OnDataAvailable(base::UnixSocket* self) override;
 
+  ProfilingSession ExpectPID(pid_t pid, ClientConfiguration cfg);
+
  private:
+  struct ProcessInfo {
+    ProcessInfo(ClientConfiguration cfg) : client_config(std::move(cfg)) {}
+    ClientConfiguration client_config;
+    std::set<base::UnixSocket*> sockets;
+  };
+
   struct Entry {
     Entry(std::unique_ptr<base::UnixSocket> s) : sock(std::move(s)) {}
     // Only here for ownership of the object.
@@ -58,14 +98,15 @@
     //
     // This does not get initialized in the ctor because the file descriptors
     // only get received after the first Receive call of the socket.
-    std::shared_ptr<ProcessMetadata> process_metadata;
+    std::shared_ptr<UnwindingMetadata> unwinding_metadata;
   };
 
   void RecordReceived(base::UnixSocket*, size_t, std::unique_ptr<uint8_t[]>);
+  void ShutdownPID(pid_t pid);
 
-  ClientConfiguration client_config_;
   std::map<base::UnixSocket*, Entry> sockets_;
-  std::map<pid_t, std::weak_ptr<ProcessMetadata>> process_metadata_;
+  std::map<pid_t, std::weak_ptr<UnwindingMetadata>> unwinding_metadata_;
+  std::map<pid_t, ProcessInfo> process_info_;
   std::function<void(UnwindingRecord)> callback_function_;
   BookkeepingThread* const bookkeeping_thread_;
 };
diff --git a/src/profiling/memory/socket_listener_unittest.cc b/src/profiling/memory/socket_listener_unittest.cc
index 225572e..21d9caf 100644
--- a/src/profiling/memory/socket_listener_unittest.cc
+++ b/src/profiling/memory/socket_listener_unittest.cc
@@ -55,8 +55,8 @@
   };
 
   BookkeepingThread actor("");
-  SocketListener listener({},  // We do not care about the sampling rate.
-                          std::move(callback_fn), &actor);
+  SocketListener listener(std::move(callback_fn), &actor);
+  auto handle = listener.ExpectPID(getpid(), {});
   MockEventListener client_listener;
   EXPECT_CALL(client_listener, OnConnect(_, _))
       .WillOnce(InvokeWithoutArgs(connected));
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 4f085fd..4f2bbae 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -135,7 +135,7 @@
   maps_.clear();
 }
 
-bool DoUnwind(WireMessage* msg, ProcessMetadata* metadata, AllocRecord* out) {
+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));
@@ -175,7 +175,7 @@
                           &msg))
     return false;
   if (msg.record_type == RecordType::Malloc) {
-    std::shared_ptr<ProcessMetadata> metadata = rec->metadata.lock();
+    std::shared_ptr<UnwindingMetadata> metadata = rec->metadata.lock();
     if (!metadata) {
       // Process has already gone away.
       return false;
diff --git a/src/profiling/memory/unwinding.h b/src/profiling/memory/unwinding.h
index aef2918..f9d0b33 100644
--- a/src/profiling/memory/unwinding.h
+++ b/src/profiling/memory/unwinding.h
@@ -40,8 +40,8 @@
   base::ScopedFile fd_;
 };
 
-struct ProcessMetadata {
-  ProcessMetadata(pid_t p, base::ScopedFile maps_fd, base::ScopedFile mem)
+struct UnwindingMetadata {
+  UnwindingMetadata(pid_t p, base::ScopedFile maps_fd, base::ScopedFile mem)
       : pid(p), maps(std::move(maps_fd)), mem_fd(std::move(mem)) {
     PERFETTO_CHECK(maps.Parse());
   }
@@ -65,7 +65,7 @@
   uint8_t* stack_;
 };
 
-bool DoUnwind(WireMessage*, ProcessMetadata* metadata, AllocRecord* out);
+bool DoUnwind(WireMessage*, UnwindingMetadata* metadata, AllocRecord* out);
 
 bool HandleUnwindingRecord(UnwindingRecord* rec, BookkeepingRecord* out);
 
diff --git a/src/profiling/memory/unwinding_fuzzer.cc b/src/profiling/memory/unwinding_fuzzer.cc
index e1fe6ee..a52d673 100644
--- a/src/profiling/memory/unwinding_fuzzer.cc
+++ b/src/profiling/memory/unwinding_fuzzer.cc
@@ -27,7 +27,7 @@
 
 int FuzzUnwinding(const uint8_t* data, size_t size) {
   UnwindingRecord record;
-  auto process_metadata = std::make_shared<ProcessMetadata>(
+  auto unwinding_metadata = std::make_shared<UnwindingMetadata>(
       getpid(), base::OpenFile("/proc/self/maps", O_RDONLY),
       base::OpenFile("/proc/self/mem", O_RDONLY));
 
@@ -35,7 +35,7 @@
   record.size = size;
   record.data.reset(new uint8_t[size]);
   memcpy(record.data.get(), data, size);
-  record.metadata = process_metadata;
+  record.metadata = unwinding_metadata;
 
   BookkeepingRecord out;
   HandleUnwindingRecord(&record, &out);
diff --git a/src/profiling/memory/unwinding_unittest.cc b/src/profiling/memory/unwinding_unittest.cc
index 25c6f48..c38ba00 100644
--- a/src/profiling/memory/unwinding_unittest.cc
+++ b/src/profiling/memory/unwinding_unittest.cc
@@ -123,7 +123,8 @@
   base::ScopedFile proc_maps(base::OpenFile("/proc/self/maps", O_RDONLY));
   base::ScopedFile proc_mem(base::OpenFile("/proc/self/mem", O_RDONLY));
   GlobalCallstackTrie callsites;
-  ProcessMetadata metadata(getpid(), std::move(proc_maps), std::move(proc_mem));
+  UnwindingMetadata metadata(getpid(), std::move(proc_maps),
+                             std::move(proc_mem));
   WireMessage msg;
   auto record = GetRecord(&msg);
   AllocRecord out;
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
index 9b97c73..bfc4fa6 100644
--- a/src/protozero/BUILD.gn
+++ b/src/protozero/BUILD.gn
@@ -31,30 +31,16 @@
     "message_handle.cc",
     "proto_decoder.cc",
     "proto_field_descriptor.cc",
+    "scattered_stream_memory_delegate.cc",
     "scattered_stream_null_delegate.cc",
     "scattered_stream_writer.cc",
   ]
 }
 
-source_set("test_support") {
-  testonly = true
-  deps = [
-    "../../gn:default_deps",
-  ]
-  public_deps = [
-    ":protozero",
-  ]
-  sources = [
-    "scattered_stream_delegate_for_testing.cc",
-    "scattered_stream_delegate_for_testing.h",
-  ]
-}
-
 source_set("unittests") {
   testonly = true
   deps = [
     ":protozero",
-    ":test_support",
     ":testing_messages_lite",
     ":testing_messages_zero",
     "../../gn:default_deps",
diff --git a/src/protozero/proto_decoder_unittest.cc b/src/protozero/proto_decoder_unittest.cc
index 6f0eda2..12f48dc 100644
--- a/src/protozero/proto_decoder_unittest.cc
+++ b/src/protozero/proto_decoder_unittest.cc
@@ -21,7 +21,7 @@
 #include "perfetto/base/utils.h"
 #include "perfetto/protozero/message.h"
 #include "perfetto/protozero/proto_utils.h"
-#include "src/protozero/scattered_stream_delegate_for_testing.h"
+#include "perfetto/protozero/scattered_stream_memory_delegate.h"
 
 namespace protozero {
 namespace {
@@ -33,7 +33,7 @@
 
 TEST(ProtoDecoder, ReadString) {
   Message message;
-  perfetto::ScatteredStreamDelegateForTesting delegate(512);
+  perfetto::ScatteredStreamMemoryDelegate delegate(512);
   ScatteredStreamWriter writer(&delegate);
   delegate.set_writer(&writer);
   message.Reset(&writer);
diff --git a/src/protozero/scattered_stream_delegate_for_testing.cc b/src/protozero/scattered_stream_memory_delegate.cc
similarity index 78%
rename from src/protozero/scattered_stream_delegate_for_testing.cc
rename to src/protozero/scattered_stream_memory_delegate.cc
index ce474ff..63e078f 100644
--- a/src/protozero/scattered_stream_delegate_for_testing.cc
+++ b/src/protozero/scattered_stream_memory_delegate.cc
@@ -14,18 +14,16 @@
  * limitations under the License.
  */
 
-#include "src/protozero/scattered_stream_delegate_for_testing.h"
+#include "perfetto/protozero/scattered_stream_memory_delegate.h"
 
 namespace perfetto {
 
-ScatteredStreamDelegateForTesting::ScatteredStreamDelegateForTesting(
-    size_t chunk_size)
+ScatteredStreamMemoryDelegate::ScatteredStreamMemoryDelegate(size_t chunk_size)
     : chunk_size_(chunk_size) {}
 
-ScatteredStreamDelegateForTesting::~ScatteredStreamDelegateForTesting() {}
+ScatteredStreamMemoryDelegate::~ScatteredStreamMemoryDelegate() {}
 
-protozero::ContiguousMemoryRange
-ScatteredStreamDelegateForTesting::GetNewBuffer() {
+protozero::ContiguousMemoryRange ScatteredStreamMemoryDelegate::GetNewBuffer() {
   PERFETTO_CHECK(writer_);
   if (!chunks_.empty()) {
     size_t used = chunk_size_ - writer_->bytes_available();
@@ -38,7 +36,7 @@
   return {begin, begin + chunk_size_};
 }
 
-std::vector<uint8_t> ScatteredStreamDelegateForTesting::StitchChunks() {
+std::vector<uint8_t> ScatteredStreamMemoryDelegate::StitchChunks() {
   std::vector<uint8_t> buffer;
   size_t i = 0;
   for (const auto& chunk : chunks_) {
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 497e020..72d22b4 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -163,6 +163,7 @@
     "slice_tracker_unittest.cc",
     "span_join_operator_table_unittest.cc",
     "thread_table_unittest.cc",
+    "trace_processor_impl_unittest.cc",
     "trace_sorter_unittest.cc",
   ]
   deps = [
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index e7a8002..245348c 100644
--- a/src/trace_processor/json_trace_parser.cc
+++ b/src/trace_processor/json_trace_parser.cc
@@ -82,9 +82,6 @@
 
 }  // namespace
 
-// static
-constexpr char JsonTraceParser::kPreamble[];
-
 JsonTraceParser::JsonTraceParser(TraceProcessorContext* context)
     : context_(context) {}
 
@@ -97,12 +94,19 @@
   const char* end = &buffer_[buffer_.size()];
 
   if (offset_ == 0) {
-    if (strncmp(buf, kPreamble, strlen(kPreamble))) {
-      buf[strlen(kPreamble)] = '\0';
-      PERFETTO_FATAL("Invalid trace preamble, expecting '%s' got '%s'",
-                     kPreamble, buf);
+    // Trace could begin in any of these ways:
+    // {"traceEvents":[{
+    // { "traceEvents": [{
+    // [{
+    // Skip up to the first '['
+    while (next != end && *next != '[') {
+      next++;
     }
-    next += strlen(kPreamble);
+    if (next == end) {
+      PERFETTO_ELOG("Failed to parse: first chunk missing opening [");
+      return false;
+    }
+    next++;
   }
 
   ProcessTracker* procs = context_->process_tracker.get();
@@ -123,8 +127,8 @@
     uint32_t tid = value["tid"].asUInt();
     uint32_t pid = value["pid"].asUInt();
     uint64_t ts = value["ts"].asLargestUInt() * 1000;
-    const char* cat = value["cat"].asCString();
-    const char* name = value["name"].asCString();
+    const char* cat = value.isMember("cat") ? value["cat"].asCString() : "";
+    const char* name = value.isMember("name") ? value["name"].asCString() : "";
     StringId cat_id = storage->InternString(cat);
     StringId name_id = storage->InternString(name);
     UniqueTid utid = procs->UpdateThread(tid, pid);
diff --git a/src/trace_processor/json_trace_parser.h b/src/trace_processor/json_trace_parser.h
index b0e39ec..bdd9378 100644
--- a/src/trace_processor/json_trace_parser.h
+++ b/src/trace_processor/json_trace_parser.h
@@ -35,8 +35,6 @@
 // and supports only explicit TRACE_EVENT_BEGIN/END events.
 class JsonTraceParser : public ChunkedTraceReader {
  public:
-  static constexpr char kPreamble[] = "{\"traceEvents\":[";
-
   explicit JsonTraceParser(TraceProcessorContext*);
   ~JsonTraceParser() override;
 
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index f799748..6f16140 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -78,7 +78,7 @@
 }
 
 TEST_F(TraceProcessorIntegrationTest, Sfgate) {
-  ASSERT_TRUE(LoadTrace("sfgate.json", strlen(JsonTraceParser::kPreamble)));
+  ASSERT_TRUE(LoadTrace("sfgate.json", strlen("{\"traceEvents\":[")));
   protos::RawQueryResult res;
   Query("select count(*), max(ts) - min(ts) from slices where utid != 0", &res);
   ASSERT_EQ(res.num_records(), 1);
@@ -86,6 +86,11 @@
   ASSERT_EQ(res.columns(1).long_values(0), 40532506000);
 }
 
+// TODO(hjd): Add trace to test_data.
+TEST_F(TraceProcessorIntegrationTest, DISABLED_AndroidBuildTrace) {
+  ASSERT_TRUE(LoadTrace("android_build_trace.json", strlen("[\n{")));
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 0cc58e4..7189c1b 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/trace_processor_impl.h"
 
 #include <sqlite3.h>
+#include <algorithm>
 #include <functional>
 
 #include "perfetto/base/time.h"
@@ -58,6 +59,32 @@
 
 namespace perfetto {
 namespace trace_processor {
+namespace {
+
+bool IsPrefix(const std::string& a, const std::string& b) {
+  return a.size() <= b.size() && b.substr(0, a.size()) == a;
+}
+
+std::string RemoveWhitespace(const std::string& input) {
+  std::string str(input);
+  str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());
+  return str;
+}
+
+}  // namespace
+
+TraceType GuessTraceType(const uint8_t* data, size_t size) {
+  if (size == 0)
+    return kUnknownTraceType;
+  std::string start(reinterpret_cast<const char*>(data),
+                    std::min<size_t>(size, 20));
+  std::string start_minus_white_space = RemoveWhitespace(start);
+  if (IsPrefix("{\"traceEvents\":[", start_minus_white_space))
+    return kJsonTraceType;
+  if (IsPrefix("[{", start_minus_white_space))
+    return kJsonTraceType;
+  return kProtoTraceType;
+}
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg) {
   sqlite3* db = nullptr;
@@ -96,15 +123,17 @@
   // If this is the first Parse() call, guess the trace type and create the
   // appropriate parser.
   if (!context_.chunk_reader) {
-    char buf[32];
-    memcpy(buf, &data[0], std::min(size, sizeof(buf)));
-    buf[sizeof(buf) - 1] = '\0';
-    const size_t kPreambleLen = strlen(JsonTraceParser::kPreamble);
-    if (strncmp(buf, JsonTraceParser::kPreamble, kPreambleLen) == 0) {
-      PERFETTO_DLOG("Legacy JSON trace detected");
-      context_.chunk_reader.reset(new JsonTraceParser(&context_));
-    } else {
-      context_.chunk_reader.reset(new ProtoTraceTokenizer(&context_));
+    TraceType trace_type = GuessTraceType(data.get(), size);
+    switch (trace_type) {
+      case kJsonTraceType:
+        PERFETTO_DLOG("Legacy JSON trace detected");
+        context_.chunk_reader.reset(new JsonTraceParser(&context_));
+        break;
+      case kProtoTraceType:
+        context_.chunk_reader.reset(new ProtoTraceTokenizer(&context_));
+        break;
+      case kUnknownTraceType:
+        return false;
     }
   }
 
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 74ccb97..25eba7e 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -35,6 +35,14 @@
 
 namespace trace_processor {
 
+enum TraceType {
+  kUnknownTraceType,
+  kProtoTraceType,
+  kJsonTraceType,
+};
+
+TraceType GuessTraceType(const uint8_t* data, size_t size);
+
 // Coordinates the loading of traces from an arbitrary source and allows
 // execution of SQL queries on the events in these traces.
 class TraceProcessorImpl : public TraceProcessor {
diff --git a/src/trace_processor/trace_processor_impl_unittest.cc b/src/trace_processor/trace_processor_impl_unittest.cc
new file mode 100644
index 0000000..a901de6
--- /dev/null
+++ b/src/trace_processor/trace_processor_impl_unittest.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/trace_processor_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+TEST(TraceProcessorImpl, GuessTraceType_Empty) {
+  const uint8_t prefix[] = "";
+  EXPECT_EQ(kUnknownTraceType, GuessTraceType(prefix, 0));
+}
+
+TEST(TraceProcessorImpl, GuessTraceType_Json) {
+  const uint8_t prefix[] = "{\"traceEvents\":[";
+  EXPECT_EQ(kJsonTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImpl, GuessTraceType_JsonWithSpaces) {
+  const uint8_t prefix[] = "\n{ \"traceEvents\": [";
+  EXPECT_EQ(kJsonTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImpl, GuessTraceType_JsonMissingTraceEvents) {
+  const uint8_t prefix[] = "[{";
+  EXPECT_EQ(kJsonTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+TEST(TraceProcessorImpl, GuessTraceType_Proto) {
+  const uint8_t prefix[] = {0x0a, 0x65, 0x18, 0x8f, 0x4e, 0x32, 0x60, 0x0a};
+  EXPECT_EQ(kProtoTraceType, GuessTraceType(prefix, sizeof(prefix)));
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/BUILD.gn b/src/traced/probes/ftrace/BUILD.gn
index 5569a56..8cddd4c 100644
--- a/src/traced/probes/ftrace/BUILD.gn
+++ b/src/traced/probes/ftrace/BUILD.gn
@@ -32,7 +32,7 @@
     "../../../protozero",
   ]
   public_deps = [
-    "../../../protozero:test_support",
+    "../../../protozero",
   ]
 
   sources = [
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 048eb41..c60c123 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -25,8 +25,8 @@
 
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/utils.h"
+#include "perfetto/protozero/scattered_stream_memory_delegate.h"
 #include "perfetto/protozero/scattered_stream_writer.h"
-#include "src/protozero/scattered_stream_delegate_for_testing.h"
 
 #include "perfetto/trace/ftrace/ftrace_event.pb.h"
 #include "perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -99,7 +99,7 @@
   ProtoProvider& operator=(const ProtoProvider&) = delete;
 
   size_t chunk_size_;
-  ScatteredStreamDelegateForTesting delegate_;
+  ScatteredStreamMemoryDelegate delegate_;
   protozero::ScatteredStreamWriter stream_;
   ZeroT writer_;
 };
diff --git a/src/traced/probes/ftrace/format_parser.cc b/src/traced/probes/ftrace/format_parser.cc
index 92a9e55..6cb1ee3 100644
--- a/src/traced/probes/ftrace/format_parser.cc
+++ b/src/traced/probes/ftrace/format_parser.cc
@@ -21,7 +21,6 @@
 #include <iosfwd>
 #include <iostream>
 #include <memory>
-#include <regex>
 #include <string>
 #include <vector>
 
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index 9ec34fc..ddff880 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -102,6 +102,12 @@
       (std::find(quirks.begin(), quirks.end(),
                  ProcessStatsConfig::DISABLE_ON_DEMAND) == quirks.end());
   poll_period_ms_ = ps_config.proc_stats_poll_ms();
+  if (poll_period_ms_ > 0 && poll_period_ms_ < 100) {
+    PERFETTO_ILOG("proc_stats_poll_ms %" PRIu32
+                  " is less than minimum of 100ms. Increasing to 100ms.",
+                  poll_period_ms_);
+    poll_period_ms_ = 100;
+  }
 }
 
 ProcessStatsDataSource::~ProcessStatsDataSource() = default;
diff --git a/src/traced/probes/sys_stats/sys_stats_data_source.cc b/src/traced/probes/sys_stats/sys_stats_data_source.cc
index a4b4717..61d869d 100644
--- a/src/traced/probes/sys_stats/sys_stats_data_source.cc
+++ b/src/traced/probes/sys_stats/sys_stats_data_source.cc
@@ -51,6 +51,16 @@
   return fd;
 }
 
+uint32_t ClampTo10Ms(uint32_t period_ms, const char* counter_name) {
+  if (period_ms > 0 && period_ms < 10) {
+    PERFETTO_ILOG("%s %" PRIu32
+                  " is less than minimum of 10ms. Increasing to 10ms.",
+                  counter_name, period_ms);
+    return 10;
+  }
+  return period_ms;
+}
+
 }  // namespace
 
 // static
@@ -102,9 +112,11 @@
   std::array<uint32_t, 3> periods_ms{};
   std::array<uint32_t, 3> ticks{};
   static_assert(periods_ms.size() == ticks.size(), "must have same size");
-  periods_ms[0] = config.meminfo_period_ms();
-  periods_ms[1] = config.vmstat_period_ms();
-  periods_ms[2] = config.stat_period_ms();
+
+  periods_ms[0] = ClampTo10Ms(config.meminfo_period_ms(), "meminfo_period_ms");
+  periods_ms[1] = ClampTo10Ms(config.vmstat_period_ms(), "vmstat_period_ms");
+  periods_ms[2] = ClampTo10Ms(config.stat_period_ms(), "stat_period_ms");
+
   tick_period_ms_ = 0;
   for (uint32_t ms : periods_ms) {
     if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index ac15a35..5e622ab 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -125,7 +125,7 @@
     "../../include/perfetto/tracing/core",
     "../../protos/perfetto/trace:lite",
     "../../protos/perfetto/trace:zero",
-    "../protozero:test_support",
+    "../protozero",
   ]
   sources = [
     "core/trace_writer_for_testing.cc",
diff --git a/src/tracing/core/trace_writer_for_testing.h b/src/tracing/core/trace_writer_for_testing.h
index 2025963..f667e31 100644
--- a/src/tracing/core/trace_writer_for_testing.h
+++ b/src/tracing/core/trace_writer_for_testing.h
@@ -17,18 +17,18 @@
 #define SRC_TRACING_CORE_TRACE_WRITER_FOR_TESTING_H_
 
 #include "perfetto/protozero/message_handle.h"
+#include "perfetto/protozero/scattered_stream_memory_delegate.h"
 #include "perfetto/trace/trace_packet.pb.h"
 #include "perfetto/tracing/core/trace_writer.h"
-#include "src/protozero/scattered_stream_delegate_for_testing.h"
 
 namespace perfetto {
 
 // A specialization of TraceWriter for testing which writes into memory
-// allocated by the ScatteredStreamDelegateForTesting.
+// allocated by the ScatteredStreamMemoryDelegate.
 // See //include/perfetto/tracing/core/trace_writer.h for docs.
 class TraceWriterForTesting : public TraceWriter {
  public:
-  // TraceWriterForTesting(const ScatteredStreamDelegateForTesting& delegate);
+  // TraceWriterForTesting(const ScatteredStreamMemoryDelegate& delegate);
   TraceWriterForTesting();
   ~TraceWriterForTesting() override;
 
@@ -45,7 +45,7 @@
   TraceWriterForTesting(const TraceWriterForTesting&) = delete;
   TraceWriterForTesting& operator=(const TraceWriterForTesting&) = delete;
 
-  ScatteredStreamDelegateForTesting delegate_;
+  ScatteredStreamMemoryDelegate delegate_;
   protozero::ScatteredStreamWriter stream_;
 
   // The packet returned via NewTracePacket(). Its owned by this class,
diff --git a/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index 8b47cf4..484e0ad 100644
--- a/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -406,6 +406,7 @@
   // TODO: Figure out a story for reconciling the various clocks.
   optional uint64 timestamp = 1;
 
+  // Kernel pid (do not confuse with userspace pid aka tgid)
   optional uint32 pid = 2;
 
   oneof event {
diff --git a/tools/gen_binary_descriptors b/tools/gen_binary_descriptors
new file mode 100755
index 0000000..513ef95
--- /dev/null
+++ b/tools/gen_binary_descriptors
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# 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.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+import os
+import re
+import sys
+import argparse
+import tempfile
+import subprocess
+import hashlib
+import textwrap
+
+SOURCE_TARGET = {
+    'protos/perfetto/config/perfetto_config.proto':
+            'src/perfetto_cmd/perfetto_config.descriptor.h',
+}
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+SCRIPT_PATH = 'tools/gen_binary_descriptors'
+
+def hash_path(path):
+  hash = hashlib.sha1()
+  with open(os.path.join(ROOT_DIR, path)) as f:
+    hash.update(f.read())
+  return hash.hexdigest()
+
+
+def check(source, target):
+  assert os.path.exists(os.path.join(ROOT_DIR, target))
+
+  with open(target, 'rb') as f:
+    s = f.read()
+
+  hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s)
+  assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes])
+  for path, expected_sha1 in hashes:
+    actual_sha1 = hash_path(os.path.join(ROOT_DIR, path))
+    assert actual_sha1 == expected_sha1, \
+        'Hash for path {} did not match'.format(path)
+
+
+def generate(source, target, protoc_path):
+  _, source_name = os.path.split(source)
+  _, target_name = os.path.split(target)
+  assert source_name.replace('.proto', '.descriptor.h') == target_name
+
+  with tempfile.NamedTemporaryFile() as fdescriptor:
+    subprocess.check_call([
+        protoc_path,
+        '--proto_path=protos',
+        '-o{}'.format(fdescriptor.name),
+        source,
+    ], cwd=ROOT_DIR)
+
+    s = fdescriptor.read()
+    constant_name = 'k' + target_name.title()[:-2].translate(None, '._')
+    binary = '{' + ', '.join('{0:#04x}'.format(ord(c)) for c in s) + '}'
+    binary = textwrap.fill(binary,
+        width=80,
+        initial_indent='    ',
+        subsequent_indent='     ')
+    include_guard = target.replace('/', '_').replace('.', '_').upper() + '_'
+
+    with open(os.path.join(ROOT_DIR, target), 'wb') as f:
+      f.write("""
+#ifndef {include_guard}
+#define {include_guard}
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
+
+// SHA1({script_path})
+// {script_hash}
+// SHA1({source_path})
+// {source_hash}
+
+// This is the proto {{proto_name}} encoded as a ProtoFileDescriptor to allow
+// for reflection without libprotobuf full/non-lite protos.
+
+namespace perfetto {{
+
+constexpr std::array<uint8_t, {size}> {constant_name}{{
+{binary}}};
+
+}}  // namespace perfetto
+
+#endif  // {include_guard}
+""".format(**{
+        'size': len(s),
+        'constant_name': constant_name,
+        'binary': binary,
+        'include_guard': include_guard,
+        'script_path': SCRIPT_PATH,
+        'script_hash': hash_path(__file__),
+        'source_path': source,
+        'source_hash': hash_path(os.path.join(source)),
+      }))
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--check-only', action='store_true')
+  parser.add_argument('--protoc')
+  args = parser.parse_args()
+
+  for source, target in SOURCE_TARGET.iteritems():
+    if args.check_only:
+        check(source, target)
+    else:
+      protoc = args.protoc
+      assert os.path.exists(protoc)
+      generate(source, target, args.protoc)
+
+if __name__ == '__main__':
+  exit(main())
diff --git a/tools/gen_merged_protos b/tools/gen_merged_protos
index c99f845..b2a1ad11 100755
--- a/tools/gen_merged_protos
+++ b/tools/gen_merged_protos
@@ -18,7 +18,6 @@
 from __future__ import print_function
 import os
 import re
-import subprocess
 import sys
 
 CONFIG_PROTOS = (
@@ -155,7 +154,6 @@
     used_type = use.group(2)
     field_number = use.group(3)
     if used_type not in types:
-      # replacement = '{}reserved {};'.format(indentation, field_number)
       replacement = '{}// removed field with id {}'.format(
           indentation, field_number)
       substitutions.append((everything, replacement))
diff --git a/tools/tmux b/tools/tmux
index 3afdb0c..ff12b42 100755
--- a/tools/tmux
+++ b/tools/tmux
@@ -116,7 +116,8 @@
 fi
 
 CONFIG_DEVICE_PATH=$CONFIG
-if [[ "$CONFIG" != ":test" ]]; then
+CMD_OPTS=""
+if [[ "$CONFIG" == *.protobuf ]]; then
   CONFIG_DEVICE_PATH=$DIR/$CONFIG.protobuf
   CONFIG_PATH=$OUT/$CONFIG.protobuf;
   if [[ ! -f $CONFIG_PATH ]]; then
@@ -124,6 +125,15 @@
     exit 1
   fi
   push $CONFIG_PATH
+elif [[ "$CONFIG" != ":test" ]]; then
+  CONFIG_DEVICE_PATH=$DIR/$CONFIG
+  CONFIG_PATH=test/configs/$CONFIG;
+  if [[ ! -f $CONFIG_PATH ]]; then
+    echo 'Config "'$CONFIG_PATH'" not known.'
+    exit 1
+  fi
+  CMD_OPTS="--txt $CMD_OPTS"
+  push $CONFIG_PATH
 fi
 
 POSTFIX=""
@@ -179,7 +189,7 @@
 tmux send-keys "$PREFIX PERFETTO_METATRACE_FILE=$DIR/mtrace $DIR/traced_probes $POSTFIX" Enter
 
 tmux select-pane -t 2
-tmux send-keys "$PREFIX $DIR/perfetto -c $CONFIG_DEVICE_PATH -o $DIR/trace $POSTFIX"
+tmux send-keys "$PREFIX $DIR/perfetto $CMD_OPTS -c $CONFIG_DEVICE_PATH -o $DIR/trace $POSTFIX"
 
 # Select consumer pane.
 tmux select-pane -t 2
diff --git a/ui/src/controller/engine.ts b/ui/src/controller/engine.ts
index e3b2c5e..8a61936 100644
--- a/ui/src/controller/engine.ts
+++ b/ui/src/controller/engine.ts
@@ -77,10 +77,16 @@
   }
 
   async getTraceTimeBounds(): Promise<TimeSpan> {
-    const maxQuery = 'select max(ts) from (select max(ts) as ts from sched ' +
-        'union all select max(ts) as ts from slices)';
-    const minQuery = 'select min(ts) from (select min(ts) as ts from sched ' +
-        'union all select min(ts) as ts from slices)';
+    const maxQuery = `select max(ts) from (
+      select max(ts) as ts from sched
+      union all select max(ts) as ts from slices
+      union all select max(ts) as ts from counters
+    )`;
+    const minQuery = `select min(ts) from (
+      select min(ts) as ts from sched
+      union all select min(ts) as ts from slices
+      union all select min(ts) as ts from counters
+    )`;
     const start = (await this.queryOneRow(minQuery))[0];
     const end = (await this.queryOneRow(maxQuery))[0];
     return new TimeSpan(start / 1e9, end / 1e9);
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 86cbbab..8e71a5a 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -22,6 +22,13 @@
 import {globals} from './globals';
 import {createPage} from './pages';
 
+const PROC_STATS_PRESETS = [
+  {label: 'never', value: null},
+  {label: '100ms', value: 100},
+  {label: '250ms', value: 250},
+  {label: '500ms', value: 500},
+];
+
 const COUNTER_PRESETS = [
   {label: 'never', value: null},
   {label: '10ms', value: 10},
@@ -797,7 +804,7 @@
               placeholder: 'never',
               value: config.procStatusPeriodMs,
               onchange: onChange<null|number>('procStatusPeriodMs'),
-              presets: COUNTER_PRESETS,
+              presets: PROC_STATS_PRESETS,
             }),
           ),