metrics: Implement getting prototext formatted metric result

Uses protozero_to_text to convert the metric proto result into
prototext, which will be used by the UI in a later CL.

Bug:161699653
Change-Id: I5cb9e3021855e6a71f756db3a3a77b6ff73346c5
diff --git a/Android.bp b/Android.bp
index 995769c..ab70cd7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8319,6 +8319,7 @@
     ":perfetto_src_trace_processor_track_event_descriptor",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_protozero_to_text",
     ":perfetto_src_trace_processor_util_util",
     "src/trace_processor/trace_processor_shell.cc",
     "src/trace_processor/util/proto_to_json.cc",
@@ -8452,6 +8453,7 @@
     ":perfetto_src_trace_processor_track_event_descriptor",
     ":perfetto_src_trace_processor_types_types",
     ":perfetto_src_trace_processor_util_descriptors",
+    ":perfetto_src_trace_processor_util_protozero_to_text",
     ":perfetto_src_trace_processor_util_util",
     ":perfetto_tools_trace_to_text_common",
     ":perfetto_tools_trace_to_text_full",
diff --git a/BUILD b/BUILD
index 9839996..9c2ebac 100644
--- a/BUILD
+++ b/BUILD
@@ -940,6 +940,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/util:protozero_to_text
+filegroup(
+    name = "src_trace_processor_util_protozero_to_text",
+    srcs = [
+        "src/trace_processor/util/protozero_to_text.cc",
+        "src/trace_processor/util/protozero_to_text.h",
+    ],
+)
+
 # GN target: //src/trace_processor/util:util
 filegroup(
     name = "src_trace_processor_util_util",
@@ -2992,6 +3001,7 @@
         ":src_trace_processor_track_event_descriptor",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_util",
     ],
     hdrs = [
@@ -3086,6 +3096,7 @@
         ":src_trace_processor_track_event_descriptor",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_util",
     ],
     visibility = [
@@ -3253,6 +3264,7 @@
         ":src_trace_processor_track_event_descriptor",
         ":src_trace_processor_types_types",
         ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_protozero_to_text",
         ":src_trace_processor_util_util",
         ":tools_trace_to_text_common",
         ":tools_trace_to_text_full",
diff --git a/include/perfetto/trace_processor/trace_processor.h b/include/perfetto/trace_processor/trace_processor.h
index 9403860..c949695 100644
--- a/include/perfetto/trace_processor/trace_processor.h
+++ b/include/perfetto/trace_processor/trace_processor.h
@@ -65,6 +65,19 @@
       const std::vector<std::string>& metric_names,
       std::vector<uint8_t>* metrics_proto) = 0;
 
+  enum MetricResultFormat {
+    kProtoText = 0,
+    kJson = 1,
+  };
+
+  // Computes metrics as the ComputeMetric function above, but instead of
+  // producing proto encoded bytes, the output argument |metrics_string| is
+  // filled with the metric formatted in the requested |format|.
+  virtual util::Status ComputeMetricText(
+      const std::vector<std::string>& metric_names,
+      MetricResultFormat format,
+      std::string* metrics_string) = 0;
+
   // Interrupts the current query. Typically used by Ctrl-C handler.
   virtual void InterruptQuery() = 0;
 
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index b15bd4f..f32745b 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -142,17 +142,27 @@
 
 // Input for the /compute_metric endpoint.
 message ComputeMetricArgs {
+  enum ResultFormat {
+    BINARY_PROTOBUF = 0;
+    TEXTPROTO = 1;
+  }
   repeated string metric_names = 1;
+  optional ResultFormat format = 2;
 }
 
 // Output for the /compute_metric endpoint.
 message ComputeMetricResult {
-  // This is meant to contain a perfetto.protos.TraceMetrics. We're using bytes
-  // instead of the actual type because we do not want to generate protozero
-  // code for the metrics protos. We always encode/decode metrics using a
-  // reflection based mechanism that does not require the compiled C++ code.
-  // This allows us to read in new protos at runtime.
-  optional bytes metrics = 1;
+  oneof result {
+    // This is meant to contain a perfetto.protos.TraceMetrics. We're using
+    // bytes instead of the actual type because we do not want to generate
+    // protozero code for the metrics protos. We always encode/decode metrics
+    // using a reflection based mechanism that does not require the compiled C++
+    // code. This allows us to read in new protos at runtime.
+    bytes metrics = 1;
+
+    // A perfetto.protos.TraceMetrics formatted as prototext.
+    string metrics_as_prototext = 3;
+  }
 
   optional string error = 2;
 }
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 5270204..a039e41 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -323,6 +323,7 @@
       "tables",
       "types",
       "util",
+      "util:protozero_to_text",
     ]
     public_deps = [
       "../../gn:sqlite",  # iterator_impl.h includes sqlite3.h.
diff --git a/src/trace_processor/metrics/chrome/test_chrome_metric.sql b/src/trace_processor/metrics/chrome/test_chrome_metric.sql
index 4cce8e1..79667fc 100644
--- a/src/trace_processor/metrics/chrome/test_chrome_metric.sql
+++ b/src/trace_processor/metrics/chrome/test_chrome_metric.sql
@@ -1,5 +1,17 @@
--- Copyright 2020 Google LLC.
--- SPDX-License-Identifier: Apache-2.0
+--
+-- Copyright 2020 The Android Open Source Project
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
 
 CREATE VIEW test_chrome_metric_output AS
-SELECT TestChromeMetric('test_value', 1))
+SELECT TestChromeMetric('test_value', 1)
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
index 0ea2233..3d2bf28 100644
--- a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
index aac8764..98efcd9 100644
--- a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ b/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -2,4 +2,4 @@
 // SHA1(tools/gen_binary_descriptors)
 // e5c244903aa00cad06faf3d126918306a7fe811e
 // SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// 12cc669e9fece972c07279164b68f914ab7c6fc8
+// 8320f306d6d5bbcb5ef6ba8cd62cc70a0994d102
diff --git a/src/trace_processor/rpc/rpc.cc b/src/trace_processor/rpc/rpc.cc
index 44f27a2..a93438c 100644
--- a/src/trace_processor/rpc/rpc.cc
+++ b/src/trace_processor/rpc/rpc.cc
@@ -271,18 +271,38 @@
   PERFETTO_TP_TRACE("RPC_COMPUTE_METRIC", [&](metatrace::Record* r) {
     for (const auto& metric : metric_names) {
       r->AddArg("Metric", metric);
+      r->AddArg("Format", std::to_string(args.format()));
     }
   });
 
-  std::vector<uint8_t> metrics_proto;
-  util::Status status =
-      trace_processor_->ComputeMetric(metric_names, &metrics_proto);
-  if (status.ok()) {
-    result->AppendBytes(
-        protos::pbzero::ComputeMetricResult::kMetricsFieldNumber,
-        metrics_proto.data(), metrics_proto.size());
-  } else {
-    result->set_error(status.message());
+  switch (args.format()) {
+    case protos::pbzero::ComputeMetricArgs::BINARY_PROTOBUF: {
+      std::vector<uint8_t> metrics_proto;
+      util::Status status =
+          trace_processor_->ComputeMetric(metric_names, &metrics_proto);
+      if (status.ok()) {
+        result->AppendBytes(
+            protos::pbzero::ComputeMetricResult::kMetricsFieldNumber,
+            metrics_proto.data(), metrics_proto.size());
+      } else {
+        result->set_error(status.message());
+      }
+      break;
+    }
+    case protos::pbzero::ComputeMetricArgs::TEXTPROTO: {
+      std::string metrics_string;
+      util::Status status = trace_processor_->ComputeMetricText(
+          metric_names, TraceProcessor::MetricResultFormat::kProtoText,
+          &metrics_string);
+      if (status.ok()) {
+        result->AppendString(
+            protos::pbzero::ComputeMetricResult::kMetricsAsPrototextFieldNumber,
+            metrics_string);
+      } else {
+        result->set_error(status.message());
+      }
+      break;
+    }
   }
   return result.SerializeAsArray();
 }
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 2540464..2ee8f60 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -196,6 +196,18 @@
   ASSERT_EQ(trace_metrics_count, 1);
 }
 
+TEST_F(TraceProcessorIntegrationTest, ComputeMetricsFormatted) {
+  std::string metric_output;
+  util::Status status = Processor()->ComputeMetricText(
+      std::vector<std::string>{"test_chrome_metric"},
+      TraceProcessor::MetricResultFormat::kProtoText, &metric_output);
+  ASSERT_TRUE(status.ok());
+  ASSERT_EQ(metric_output,
+            "test_chrome_metric: {\n"
+            "  test_value: 1\n"
+            "}");
+}
+
 // TODO(hjd): Add trace to test_data.
 TEST_F(TraceProcessorIntegrationTest, DISABLED_AndroidBuildTrace) {
   ASSERT_TRUE(LoadTrace("android_build_trace.json", strlen("[\n{")).ok());
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 5ca9bbe..2875d91 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -52,6 +52,7 @@
 #include "src/trace_processor/sqlite/window_operator_table.h"
 #include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/protozero_to_text.h"
 
 #include "protos/perfetto/trace/perfetto/perfetto_metatrace.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
@@ -1002,6 +1003,29 @@
                                  root_descriptor, metrics_proto);
 }
 
+util::Status TraceProcessorImpl::ComputeMetricText(
+    const std::vector<std::string>& metric_names,
+    TraceProcessor::MetricResultFormat format,
+    std::string* metrics_string) {
+  std::vector<uint8_t> metrics_proto;
+  util::Status status = ComputeMetric(metric_names, &metrics_proto);
+  if (!status.ok())
+    return status;
+  switch (format) {
+    case TraceProcessor::MetricResultFormat::kProtoText:
+      *metrics_string = protozero_to_text::ProtozeroToText(
+          pool_, ".perfetto.protos.TraceMetrics",
+          protozero::ConstBytes{metrics_proto.data(), metrics_proto.size()},
+          protozero_to_text::kIncludeNewLines);
+      break;
+    case TraceProcessor::MetricResultFormat::kJson:
+      // TODO(dproy): Implement this.
+      PERFETTO_FATAL("Json formatted metrics not supported yet.");
+      break;
+  }
+  return status;
+}
+
 std::vector<uint8_t> TraceProcessorImpl::GetMetricDescriptors() {
   return pool_.SerializeAsDescriptorSet();
 }
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 7727600..cb9ef9f 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -64,6 +64,10 @@
   util::Status ComputeMetric(const std::vector<std::string>& metric_names,
                              std::vector<uint8_t>* metrics) override;
 
+  util::Status ComputeMetricText(const std::vector<std::string>& metric_names,
+                                 TraceProcessor::MetricResultFormat format,
+                                 std::string* metrics_string) override;
+
   std::vector<uint8_t> GetMetricDescriptors() override;
 
   void InterruptQuery() override;
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 7b4a944..efd3557 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -58,6 +58,7 @@
   sources = [ "protozero_to_text_unittests.cc" ]
   testonly = true
   deps = [
+    ":descriptors",
     ":protozero_to_text",
     "..:track_event_descriptor",
     "../../../gn:default_deps",
diff --git a/src/trace_processor/util/protozero_to_text.cc b/src/trace_processor/util/protozero_to_text.cc
index cc0f659..7a9eb38 100644
--- a/src/trace_processor/util/protozero_to_text.cc
+++ b/src/trace_processor/util/protozero_to_text.cc
@@ -11,6 +11,8 @@
 
 namespace perfetto {
 namespace trace_processor {
+namespace protozero_to_text {
+
 namespace {
 
 // Recursively determine the size of all the string like things passed in the
@@ -53,7 +55,7 @@
                                            const protozero::Field& field,
                                            const std::string& separator,
                                            const std::string& indent,
-                                           DescriptorPool* pool,
+                                           const DescriptorPool& pool,
                                            std::string* out) {
   using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
   switch (fd.type()) {
@@ -100,12 +102,15 @@
     case FieldDescriptorProto::TYPE_STRING:
       StrAppend(out, separator, indent, fd.name(), ": ", field.as_std_string());
       return;
+    case FieldDescriptorProto::TYPE_BYTES:
+      // TODO(dproy): Write a bytes to hex function here when we need it.
+      PERFETTO_FATAL("Bytes field cannot be converted to prototext.");
     case FieldDescriptorProto::TYPE_ENUM: {
       auto opt_enum_descriptor_idx =
-          pool->FindDescriptorIdx(fd.resolved_type_name());
+          pool.FindDescriptorIdx(fd.resolved_type_name());
       PERFETTO_DCHECK(opt_enum_descriptor_idx);
       auto opt_enum_string =
-          pool->descriptors()[*opt_enum_descriptor_idx].FindEnumString(
+          pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
               field.as_int32());
       PERFETTO_DCHECK(opt_enum_string);
       StrAppend(out, separator, indent, fd.name(), ": ", *opt_enum_string);
@@ -134,21 +139,20 @@
 // |type| and will use |pool| to look up the |type|. All output will be placed
 // in |output| and between fields |separator| will be placed. When called for
 // |indents| will be increased by 2 spaces to improve readability.
-void ProtozeroToText(const std::string& type,
-                     protozero::ConstBytes protobytes,
-                     bool include_new_lines,
-                     DescriptorPool* pool,
-                     std::string* indents,
-                     std::string* output) {
-  auto opt_proto_descriptor_idx = pool->FindDescriptorIdx(type);
+void ProtozeroToTextInternal(const std::string& type,
+                             protozero::ConstBytes protobytes,
+                             NewLinesMode new_lines_mode,
+                             const DescriptorPool& pool,
+                             std::string* indents,
+                             std::string* output) {
+  auto opt_proto_descriptor_idx = pool.FindDescriptorIdx(type);
   PERFETTO_DCHECK(opt_proto_descriptor_idx);
-  auto& proto_descriptor = pool->descriptors()[*opt_proto_descriptor_idx];
+  auto& proto_descriptor = pool.descriptors()[*opt_proto_descriptor_idx];
+  bool include_new_lines = new_lines_mode == kIncludeNewLines;
 
   protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
   for (auto field = decoder.ReadField(); field.valid();
        field = decoder.ReadField()) {
-    // Since this is only used in debugging or tests we should always have a
-    // valid compiled in binary descriptor.
     auto opt_field_descriptor_idx =
         proto_descriptor.FindFieldIdxByTag(field.id());
     PERFETTO_DCHECK(opt_field_descriptor_idx);
@@ -165,8 +169,9 @@
         StrAppend(output, output->empty() ? "" : " ", field_descriptor.name(),
                   ": {");
       }
-      ProtozeroToText(field_descriptor.resolved_type_name(), field.as_bytes(),
-                      include_new_lines, pool, indents, output);
+      ProtozeroToTextInternal(field_descriptor.resolved_type_name(),
+                              field.as_bytes(), new_lines_mode, pool, indents,
+                              output);
       if (include_new_lines) {
         DecreaseIndents(indents);
         StrAppend(output, "\n", *indents, "}");
@@ -183,28 +188,35 @@
   PERFETTO_DCHECK(decoder.bytes_left() == 0);
 }
 
-std::string ProtozeroToText(const std::string& type,
+}  // namespace
+
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
                             protozero::ConstBytes protobytes,
-                            bool include_new_lines) {
+                            NewLinesMode new_lines_mode) {
   std::string indent = "";
   std::string final_result;
+  ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
+                          &final_result);
+  return final_result;
+}
+
+std::string DebugTrackEventProtozeroToText(const std::string& type,
+                                           protozero::ConstBytes protobytes) {
   DescriptorPool pool;
   auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
                                               kTrackEventDescriptor.size());
   PERFETTO_DCHECK(status.ok());
-  ProtozeroToText(type, protobytes, include_new_lines, &pool, &indent,
-                  &final_result);
-  return final_result;
+  return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
 }
-}  // namespace
-
-std::string DebugProtozeroToText(const std::string& type,
-                                 protozero::ConstBytes protobytes) {
-  return ProtozeroToText(type, protobytes, /* include_new_lines = */ true);
-}
-std::string ShortDebugProtozeroToText(const std::string& type,
-                                      protozero::ConstBytes protobytes) {
-  return ProtozeroToText(type, protobytes, /* include_new_lines = */ false);
+std::string ShortDebugTrackEventProtozeroToText(
+    const std::string& type,
+    protozero::ConstBytes protobytes) {
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  PERFETTO_DCHECK(status.ok());
+  return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
 }
 
 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
@@ -225,6 +237,6 @@
   }
   return *opt_enum_string;
 }
-
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/util/protozero_to_text.h b/src/trace_processor/util/protozero_to_text.h
index e418926..9e7bae4 100644
--- a/src/trace_processor/util/protozero_to_text.h
+++ b/src/trace_processor/util/protozero_to_text.h
@@ -24,15 +24,36 @@
 namespace perfetto {
 namespace trace_processor {
 
+class DescriptorPool;
+
+namespace protozero_to_text {
+
+enum NewLinesMode {
+  kIncludeNewLines = 0,
+  kSkipNewLines,
+};
+
 // Given a protozero message |protobytes| which is of fully qualified name
-// |type|. We will convert this into a text proto format string.
+// |type| within TrackEvent proto messages, we will convert this into a text
+// proto format string.
 //
-// DebugProtozeroToText will use new lines between fields, and
-// ShortDebugProtozeroToText will use only a single space.
-std::string DebugProtozeroToText(const std::string& type,
-                                 protozero::ConstBytes protobytes);
-std::string ShortDebugProtozeroToText(const std::string& type,
-                                      protozero::ConstBytes protobytes);
+// DebugTrackEventProtozeroToText will use new lines between fields, and
+// ShortDebugTrackEventProtozeroToText will use only a single space.
+std::string DebugTrackEventProtozeroToText(const std::string& type,
+                                           protozero::ConstBytes protobytes);
+std::string ShortDebugTrackEventProtozeroToText(
+    const std::string& type,
+    protozero::ConstBytes protobytes);
+
+// Given a protozero message |protobytes| which is of fully qualified name
+// |type|, convert this into a text proto format string. All types used in
+// message definition of |type| must be available in |pool|. If
+// |new_lines_modes| == kIncludeNewLines, new lines will be used between fields,
+// otherwise only a space will be used.
+std::string ProtozeroToText(const DescriptorPool& pool,
+                            const std::string& type,
+                            protozero::ConstBytes protobytes,
+                            NewLinesMode new_lines_mode);
 
 // Allow the conversion from a protozero enum to a string. The template is just
 // to allow easy enum passing since we will do the explicit cast to a int32_t
@@ -43,6 +64,7 @@
   return ProtozeroEnumToText(type, static_cast<int32_t>(enum_value));
 }
 
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/util/protozero_to_text_unittests.cc b/src/trace_processor/util/protozero_to_text_unittests.cc
index e172dd6..0ea1e76 100644
--- a/src/trace_processor/util/protozero_to_text_unittests.cc
+++ b/src/trace_processor/util/protozero_to_text_unittests.cc
@@ -19,10 +19,14 @@
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
+#include "src/trace_processor/importers/proto/track_event.descriptor.h"
+#include "src/trace_processor/util/descriptors.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
+namespace protozero_to_text {
+
 namespace {
 
 constexpr size_t kChunkSize = 42;
@@ -36,13 +40,14 @@
   msg->set_track_uuid(4);
   msg->set_timestamp_delta_us(3);
   auto binary_proto = msg.SerializeAsArray();
-  EXPECT_EQ("track_uuid: 4\ntimestamp_delta_us: 3",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+  EXPECT_EQ(
+      "track_uuid: 4\ntimestamp_delta_us: 3",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
   EXPECT_EQ(
       "track_uuid: 4 timestamp_delta_us: 3",
-      ShortDebugProtozeroToText(
+      ShortDebugTrackEventProtozeroToText(
           ".perfetto.protos.TrackEvent",
           protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 }
@@ -60,7 +65,8 @@
   msg->set_timestamp_delta_us(3);
   auto binary_proto = msg.SerializeAsArray();
 
-  EXPECT_EQ(R"(track_uuid: 4
+  EXPECT_EQ(
+      R"(track_uuid: 4
 cc_scheduler_state: {
   deadline_us: 7
   state_machine: {
@@ -71,15 +77,15 @@
   observing_begin_frame_source: true
 }
 timestamp_delta_us: 3)",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 
   EXPECT_EQ(
       "track_uuid: 4 cc_scheduler_state: { deadline_us: 7 state_machine: { "
       "minor_state: { commit_count: 8 } } observing_begin_frame_source: true } "
       "timestamp_delta_us: 3",
-      ShortDebugProtozeroToText(
+      ShortDebugTrackEventProtozeroToText(
           ".perfetto.protos.TrackEvent",
           protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
 }
@@ -89,14 +95,83 @@
   protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
   msg->set_type(TrackEvent::TYPE_SLICE_BEGIN);
   auto binary_proto = msg.SerializeAsArray();
-  EXPECT_EQ("type: TYPE_SLICE_BEGIN",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
-  EXPECT_EQ("type: TYPE_SLICE_BEGIN",
-            DebugProtozeroToText(".perfetto.protos.TrackEvent",
-                                 protozero::ConstBytes{binary_proto.data(),
-                                                       binary_proto.size()}));
+  EXPECT_EQ(
+      "type: TYPE_SLICE_BEGIN",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
+  EXPECT_EQ(
+      "type: TYPE_SLICE_BEGIN",
+      DebugTrackEventProtozeroToText(
+          ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()}));
+}
+
+TEST(ProtozeroToTextTest, CustomDescriptorPoolBasic) {
+  using perfetto::protos::pbzero::TrackEvent;
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  msg->set_track_uuid(4);
+  msg->set_timestamp_delta_us(3);
+  auto binary_proto = msg.SerializeAsArray();
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ("track_uuid: 4\ntimestamp_delta_us: 3",
+            ProtozeroToText(
+                pool, ".perfetto.protos.TrackEvent",
+                protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
+                kIncludeNewLines));
+  EXPECT_EQ("track_uuid: 4 timestamp_delta_us: 3",
+            ProtozeroToText(
+                pool, ".perfetto.protos.TrackEvent",
+                protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
+                kSkipNewLines));
+}
+
+TEST(ProtozeroToTextTest, CustomDescriptorPoolNestedMsg) {
+  using perfetto::protos::pbzero::TrackEvent;
+  protozero::HeapBuffered<TrackEvent> msg{kChunkSize, kChunkSize};
+  msg->set_track_uuid(4);
+  auto* state = msg->set_cc_scheduler_state();
+  state->set_deadline_us(7);
+  auto* machine = state->set_state_machine();
+  auto* minor_state = machine->set_minor_state();
+  minor_state->set_commit_count(8);
+  state->set_observing_begin_frame_source(true);
+  msg->set_timestamp_delta_us(3);
+  auto binary_proto = msg.SerializeAsArray();
+
+  DescriptorPool pool;
+  auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
+                                              kTrackEventDescriptor.size());
+  ASSERT_TRUE(status.ok());
+
+  EXPECT_EQ(
+      R"(track_uuid: 4
+cc_scheduler_state: {
+  deadline_us: 7
+  state_machine: {
+    minor_state: {
+      commit_count: 8
+    }
+  }
+  observing_begin_frame_source: true
+}
+timestamp_delta_us: 3)",
+      ProtozeroToText(
+          pool, ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
+          kIncludeNewLines));
+
+  EXPECT_EQ(
+      "track_uuid: 4 cc_scheduler_state: { deadline_us: 7 state_machine: { "
+      "minor_state: { commit_count: 8 } } observing_begin_frame_source: true } "
+      "timestamp_delta_us: 3",
+      ProtozeroToText(
+          pool, ".perfetto.protos.TrackEvent",
+          protozero::ConstBytes{binary_proto.data(), binary_proto.size()},
+          kSkipNewLines));
 }
 
 TEST(ProtozeroToTextTest, EnumToString) {
@@ -106,5 +181,6 @@
                                 TrackEvent::TYPE_SLICE_END));
 }
 }  // namespace
+}  // namespace protozero_to_text
 }  // namespace trace_processor
 }  // namespace perfetto