Implement histogram perf results writer.

This will be used by WebRTC tests. It converts results exactly the
same as our downstream implementation (histogram_util).

This implementation should be pretty feature complete, or at least
enough to start testing the end-to-end flow. I will set up some
experimental recipe code and see if this actually makes it into the
dashboard.

Note: needs some catapult changes to land first and be rolled
into Chromium, and then WebRTC.

Bug: chromium:1029452
Change-Id: I939046929652fc27b8fcb18af54bde22886d9228
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/166172
Commit-Queue: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30436}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 464f44f..4617dd0 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -226,7 +226,6 @@
     "testsupport/perf_test.h",
     "testsupport/perf_test_graphjson_writer.cc",
     "testsupport/perf_test_graphjson_writer.h",
-    "testsupport/perf_test_histogram_writer.cc",
     "testsupport/perf_test_histogram_writer.h",
     "testsupport/perf_test_result_writer.h",
   ]
@@ -234,9 +233,19 @@
     "../api:array_view",
     "../rtc_base:checks",
     "../rtc_base:criticalsection",
+    "../rtc_base:logging",
     "../rtc_base:rtc_numerics",
     "//third_party/abseil-cpp/absl/flags:flag",
   ]
+  if (rtc_enable_protobuf) {
+    sources += [ "testsupport/perf_test_histogram_writer.cc" ]
+    deps += [
+      "//third_party/catapult/tracing/tracing:histogram",
+      "//third_party/catapult/tracing/tracing:reserved_infos",
+    ]
+  } else {
+    sources += [ "testsupport/perf_test_histogram_writer_no_protobuf.cc" ]
+  }
 }
 
 if (is_ios) {
@@ -502,6 +511,11 @@
       "testsupport/yuv_frame_writer_unittest.cc",
     ]
 
+    if (rtc_enable_protobuf) {
+      sources += [ "testsupport/perf_test_histogram_writer_unittest.cc" ]
+      deps += [ "//third_party/catapult/tracing/tracing:histogram" ]
+    }
+
     data = test_support_unittests_resources
     if (is_android) {
       deps += [ "//testing/android/native_test:native_test_support" ]
diff --git a/test/testsupport/DEPS b/test/testsupport/DEPS
new file mode 100644
index 0000000..6f6150a
--- /dev/null
+++ b/test/testsupport/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  # Histogram C++ API, used by perf tests.
+  "+third_party/catapult/tracing/tracing/value"
+]
diff --git a/test/testsupport/perf_test.cc b/test/testsupport/perf_test.cc
index eedb0c8..ae9ce6e 100644
--- a/test/testsupport/perf_test.cc
+++ b/test/testsupport/perf_test.cc
@@ -17,7 +17,6 @@
 #include <sstream>
 #include <vector>
 
-#include "absl/flags/flag.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/critical_section.h"
 #include "test/testsupport/perf_test_graphjson_writer.h"
@@ -213,8 +212,8 @@
   GetResultsLinePrinter().SetOutput(output);
 }
 
-std::string GetPerfResultsJSON() {
-  return GetPerfWriter().ToJSON();
+std::string GetPerfResults() {
+  return GetPerfWriter().Serialize();
 }
 
 void PrintPlottableResults(const std::vector<std::string>& desired_graphs) {
@@ -222,10 +221,10 @@
 }
 
 void WritePerfResults(const std::string& output_path) {
-  std::string json_results = GetPerfResultsJSON();
-  std::fstream json_file(output_path, std::fstream::out);
-  json_file << json_results;
-  json_file.close();
+  std::string results = GetPerfResults();
+  std::fstream output(output_path, std::fstream::out);
+  output << results;
+  output.close();
 }
 
 void PrintResult(const std::string& measurement,
diff --git a/test/testsupport/perf_test.h b/test/testsupport/perf_test.h
index 0550e44..53de669 100644
--- a/test/testsupport/perf_test.h
+++ b/test/testsupport/perf_test.h
@@ -15,6 +15,7 @@
 #include <string>
 #include <vector>
 
+#include "absl/flags/flag.h"
 #include "api/array_view.h"
 #include "rtc_base/numerics/samples_stats_counter.h"
 
@@ -88,9 +89,13 @@
                  const bool important,
                  ImproveDirection improve_direction = ImproveDirection::kNone);
 
-// Returns all perf results to date in a JSON string formatted as described in
-// https://github.com/catapult-project/catapult/blob/master/dashboard/docs/data-format.md
-std::string GetPerfResultsJSON();
+// If --write_histogram_proto_json=false, this returns all perf results to date
+// in a JSON string formatted as described in dashboard/docs/data-format.md
+// in https://github.com/catapult-project/catapult/blob/master/. If
+// --write_histogram_proto_json=true, returns a string-encoded proto as
+// described in tracing/tracing/proto/histogram.proto in
+// https://github.com/catapult-project/catapult/blob/master/.
+std::string GetPerfResults();
 
 // Print into stdout plottable metrics for further post processing.
 // |desired_graphs| - list of metrics, that should be plotted. If empty - all
@@ -98,18 +103,20 @@
 // they will be skipped.
 void PrintPlottableResults(const std::vector<std::string>& desired_graphs);
 
-// Writes the JSON representation of the perf results returned by
-// GetPerfResultsJSON() to the file in output_path.
+// Call GetPerfResults() and write its output to a file.
 void WritePerfResults(const std::string& output_path);
 
 // By default, perf results are printed to stdout. Set the FILE* to where they
 // should be printing instead.
 void SetPerfResultsOutput(FILE* output);
 
-// You shouldn't use this function. It's only used to test the functions above.
+// Only for use by tests.
 void ClearPerfResults();
 
 }  // namespace test
 }  // namespace webrtc
 
+// Only for use by tests.
+ABSL_DECLARE_FLAG(bool, write_histogram_proto_json);
+
 #endif  // TEST_TESTSUPPORT_PERF_TEST_H_
diff --git a/test/testsupport/perf_test_graphjson_writer.cc b/test/testsupport/perf_test_graphjson_writer.cc
index 5a8ee64..cf49b29 100644
--- a/test/testsupport/perf_test_graphjson_writer.cc
+++ b/test/testsupport/perf_test_graphjson_writer.cc
@@ -110,7 +110,7 @@
     graphs_[graph_name].push_back(json_stream.str());
   }
 
-  std::string ToJSON() const {
+  std::string Serialize() const {
     std::ostringstream json_stream;
     json_stream << R"({"format_version":"1.0",)";
     json_stream << R"("charts":{)";
diff --git a/test/testsupport/perf_test_histogram_writer.cc b/test/testsupport/perf_test_histogram_writer.cc
index d82294b..523834b 100644
--- a/test/testsupport/perf_test_histogram_writer.cc
+++ b/test/testsupport/perf_test_histogram_writer.cc
@@ -13,15 +13,180 @@
 #include <stdlib.h>
 
 #include <map>
+#include <memory>
+
+#include "rtc_base/critical_section.h"
+#include "rtc_base/logging.h"
+#include "third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.h"
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
 
 namespace webrtc {
 namespace test {
 
-namespace {}  // namespace
+namespace {
+
+namespace proto = catapult::tracing::tracing::proto;
+
+std::string AsJsonString(const std::string string) {
+  return "\"" + string + "\"";
+}
+
+class PerfTestHistogramWriter : public PerfTestResultWriter {
+ public:
+  PerfTestHistogramWriter() : crit_() {}
+  void ClearResults() override {
+    rtc::CritScope lock(&crit_);
+    histograms_.clear();
+  }
+
+  void LogResult(const std::string& graph_name,
+                 const std::string& trace_name,
+                 const double value,
+                 const std::string& units,
+                 const bool important,
+                 ImproveDirection improve_direction) override {
+    (void)important;
+    AddSample(graph_name, trace_name, value, units, improve_direction);
+  }
+  void LogResultMeanAndError(const std::string& graph_name,
+                             const std::string& trace_name,
+                             const double mean,
+                             const double error,
+                             const std::string& units,
+                             const bool important,
+                             ImproveDirection improve_direction) override {
+    RTC_LOG(LS_WARNING) << "Discarding stddev, not supported by histograms";
+    (void)error;
+    (void)important;
+
+    AddSample(graph_name, trace_name, mean, units, improve_direction);
+  }
+  void LogResultList(const std::string& graph_name,
+                     const std::string& trace_name,
+                     const rtc::ArrayView<const double> values,
+                     const std::string& units,
+                     const bool important,
+                     ImproveDirection improve_direction) override {
+    (void)important;
+    for (double value : values) {
+      AddSample(graph_name, trace_name, value, units, improve_direction);
+    }
+  }
+  std::string Serialize() const override {
+    proto::HistogramSet histogram_set;
+
+    rtc::CritScope lock(&crit_);
+    for (const auto& histogram : histograms_) {
+      std::unique_ptr<proto::Histogram> proto = histogram.second->toProto();
+      histogram_set.mutable_histograms()->AddAllocated(proto.release());
+    }
+
+    std::string output;
+    bool ok = histogram_set.SerializeToString(&output);
+    RTC_DCHECK(ok) << "Failed to serialize histogram set to string";
+    return output;
+  }
+
+ private:
+  void AddSample(const std::string& original_graph_name,
+                 const std::string& trace_name,
+                 const double value,
+                 const std::string& units,
+                 ImproveDirection improve_direction) {
+    // WebRTC annotates the units into the metric name when they are not
+    // supported by the Histogram API.
+    std::string graph_name = original_graph_name;
+    if (units == "dB") {
+      graph_name += "_dB";
+    } else if (units == "fps") {
+      graph_name += "_fps";
+    } else if (units == "%") {
+      graph_name += "_%";
+    }
+
+    // Lookup on graph name + trace name (or measurement + story in catapult
+    // parlance). There should be several histograms with the same measurement
+    // if they're for different stories.
+    std::string measurement_and_story = graph_name + trace_name;
+    rtc::CritScope lock(&crit_);
+    if (histograms_.count(measurement_and_story) == 0) {
+      proto::UnitAndDirection unit = ParseUnit(units, improve_direction);
+      std::unique_ptr<catapult::HistogramBuilder> builder =
+          std::make_unique<catapult::HistogramBuilder>(graph_name, unit);
+      histograms_[measurement_and_story] = std::move(builder);
+
+      proto::Diagnostic stories;
+      proto::GenericSet* generic_set = stories.mutable_generic_set();
+      generic_set->add_values(AsJsonString(trace_name));
+      histograms_[measurement_and_story]->AddDiagnostic(
+          catapult::kStoriesDiagnostic, stories);
+    }
+
+    if (units == "bps") {
+      // Bps has been interpreted as bits per second in WebRTC tests.
+      histograms_[measurement_and_story]->AddSample(value / 8);
+    } else {
+      histograms_[measurement_and_story]->AddSample(value);
+    }
+  }
+
+  proto::UnitAndDirection ParseUnit(const std::string& units,
+                                    ImproveDirection improve_direction) {
+    RTC_DCHECK(units.find('_') == std::string::npos)
+        << "The unit_bigger|smallerIsBetter syntax isn't supported in WebRTC, "
+           "use the enum instead.";
+
+    proto::UnitAndDirection result;
+    result.set_improvement_direction(ParseDirection(improve_direction));
+    if (units == "bps") {
+      result.set_unit(proto::BYTES_PER_SECOND);
+    } else if (units == "dB") {
+      result.set_unit(proto::UNITLESS);
+    } else if (units == "fps") {
+      result.set_unit(proto::HERTZ);
+    } else if (units == "frames") {
+      result.set_unit(proto::COUNT);
+    } else if (units == "ms") {
+      result.set_unit(proto::MS_BEST_FIT_FORMAT);
+    } else if (units == "%") {
+      result.set_unit(proto::UNITLESS);
+    } else {
+      proto::Unit unit = catapult::UnitFromJsonUnit(units);
+
+      // UnitFromJsonUnit returns UNITLESS if it doesn't recognize the unit.
+      if (unit == proto::UNITLESS && units != "unitless") {
+        RTC_LOG(LS_WARNING) << "Unit " << units << " is unsupported.";
+      }
+
+      result.set_unit(unit);
+    }
+    return result;
+  }
+
+  proto::ImprovementDirection ParseDirection(
+      ImproveDirection improve_direction) {
+    switch (improve_direction) {
+      case ImproveDirection::kNone:
+        return proto::NOT_SPECIFIED;
+      case ImproveDirection::kSmallerIsBetter:
+        return proto::SMALLER_IS_BETTER;
+      case ImproveDirection::kBiggerIsBetter:
+        return proto::BIGGER_IS_BETTER;
+      default:
+        RTC_NOTREACHED() << "Invalid enum value " << improve_direction;
+    }
+  }
+
+ private:
+  rtc::CriticalSection crit_;
+  std::map<std::string, std::unique_ptr<catapult::HistogramBuilder>> histograms_
+      RTC_GUARDED_BY(&crit_);
+};
+
+}  // namespace
 
 PerfTestResultWriter* CreateHistogramWriter() {
-  RTC_CHECK(false) << "Not implemented";
-  return nullptr;
+  return new PerfTestHistogramWriter();
 }
 
 }  // namespace test
diff --git a/test/testsupport/perf_test_histogram_writer_no_protobuf.cc b/test/testsupport/perf_test_histogram_writer_no_protobuf.cc
new file mode 100644
index 0000000..17685cc
--- /dev/null
+++ b/test/testsupport/perf_test_histogram_writer_no_protobuf.cc
@@ -0,0 +1,22 @@
+/*
+ *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "test/testsupport/perf_test_histogram_writer.h"
+
+namespace webrtc {
+namespace test {
+
+PerfTestResultWriter* CreateHistogramWriter() {
+  RTC_NOTREACHED() << "Cannot run perf tests with rtc_enable_protobuf = false. "
+                      "Perf write results as protobufs.";
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/testsupport/perf_test_histogram_writer_unittest.cc b/test/testsupport/perf_test_histogram_writer_unittest.cc
new file mode 100644
index 0000000..6b083d6
--- /dev/null
+++ b/test/testsupport/perf_test_histogram_writer_unittest.cc
@@ -0,0 +1,197 @@
+/*
+ *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "test/testsupport/perf_test_histogram_writer.h"
+
+#include <memory>
+#include <string>
+
+#include "test/gtest.h"
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
+
+namespace webrtc {
+namespace test {
+
+namespace proto = catapult::tracing::tracing::proto;
+
+TEST(PerfHistogramWriterUnittest, TestSimpleHistogram) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("-", "-", 0, "ms", false, ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  EXPECT_TRUE(histogram_set.ParseFromString(writer->Serialize()))
+      << "Expected valid histogram set";
+
+  ASSERT_EQ(histogram_set.histograms_size(), 1);
+}
+
+TEST(PerfHistogramWriterUnittest, WritesSamplesAndUserStory) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement", "user_story", 15e7, "Hz", false,
+                    ImproveDirection::kBiggerIsBetter);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.name(), "measurement");
+
+  EXPECT_EQ(hist1.unit().unit(), proto::HERTZ);
+  EXPECT_EQ(hist1.unit().improvement_direction(), proto::BIGGER_IS_BETTER);
+
+  EXPECT_EQ(hist1.sample_values_size(), 1);
+  EXPECT_EQ(hist1.sample_values(0), 15e7);
+
+  EXPECT_EQ(hist1.diagnostics().diagnostic_map().count("stories"), 1u);
+  const proto::Diagnostic& stories =
+      hist1.diagnostics().diagnostic_map().at("stories");
+  ASSERT_EQ(stories.generic_set().values_size(), 1);
+  EXPECT_EQ(stories.generic_set().values(0), "\"user_story\"");
+}
+
+TEST(PerfHistogramWriterUnittest, WritesOneHistogramPerMeasurementAndStory) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement", "story1", 1, "ms", false,
+                    ImproveDirection::kNone);
+  writer->LogResult("measurement", "story1", 2, "ms", false,
+                    ImproveDirection::kNone);
+  writer->LogResult("measurement", "story2", 2, "ms", false,
+                    ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  ASSERT_EQ(histogram_set.histograms_size(), 2);
+
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+  const proto::Histogram& hist2 = histogram_set.histograms(1);
+
+  EXPECT_EQ(hist1.name(), "measurement");
+  EXPECT_EQ(hist2.name(), "measurement");
+
+  const proto::Diagnostic& stories1 =
+      hist1.diagnostics().diagnostic_map().at("stories");
+  EXPECT_EQ(stories1.generic_set().values(0), "\"story1\"");
+  EXPECT_EQ(hist1.sample_values_size(), 2);
+
+  const proto::Diagnostic& stories2 =
+      hist2.diagnostics().diagnostic_map().at("stories");
+  EXPECT_EQ(stories2.generic_set().values(0), "\"story2\"");
+  EXPECT_EQ(hist2.sample_values_size(), 1);
+}
+
+TEST(PerfHistogramWriterUnittest, IgnoresError) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResultMeanAndError("-", "-", 17, 12345, "ms", false,
+                                ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.running().mean(), 17);
+  EXPECT_EQ(hist1.running().variance(), 0) << "The error should be ignored.";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesDecibelIntoMeasurementName) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement", "-", 0, "dB", false,
+                    ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.unit().unit(), proto::UNITLESS)
+      << "dB should map to unitless";
+  EXPECT_EQ(hist1.name(), "measurement_dB") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesFpsIntoMeasurementName) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement", "-", 0, "fps", false,
+                    ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.unit().unit(), proto::HERTZ) << "fps should map to hertz";
+  EXPECT_EQ(hist1.name(), "measurement_fps") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesPercentIntoMeasurementName) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement", "-", 0, "%", false, ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.unit().unit(), proto::UNITLESS)
+      << "percent should map to hertz";
+  EXPECT_EQ(hist1.name(), "measurement_%") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, BitsPerSecondIsConvertedToBytes) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("-", "-", 1024, "bps", false, ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+  EXPECT_EQ(hist1.sample_values(0), 128) << "1024 bits = 128 bytes";
+}
+
+TEST(PerfHistogramWriterUnittest, ParsesDirection) {
+  std::unique_ptr<PerfTestResultWriter> writer =
+      std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+  writer->LogResult("measurement1", "-", 0, "bps", false,
+                    ImproveDirection::kBiggerIsBetter);
+  writer->LogResult("measurement2", "-", 0, "frames", false,
+                    ImproveDirection::kSmallerIsBetter);
+  writer->LogResult("measurement3", "-", 0, "sigma", false,
+                    ImproveDirection::kNone);
+
+  proto::HistogramSet histogram_set;
+  histogram_set.ParseFromString(writer->Serialize());
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+  const proto::Histogram& hist2 = histogram_set.histograms(1);
+  const proto::Histogram& hist3 = histogram_set.histograms(2);
+
+  EXPECT_EQ(hist1.unit().unit(), proto::BYTES_PER_SECOND);
+  EXPECT_EQ(hist1.unit().improvement_direction(), proto::BIGGER_IS_BETTER);
+
+  EXPECT_EQ(hist2.unit().unit(), proto::COUNT);
+  EXPECT_EQ(hist2.unit().improvement_direction(), proto::SMALLER_IS_BETTER);
+
+  EXPECT_EQ(hist3.unit().unit(), proto::SIGMA);
+  EXPECT_EQ(hist3.unit().improvement_direction(), proto::NOT_SPECIFIED);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/testsupport/perf_test_result_writer.h b/test/testsupport/perf_test_result_writer.h
index 5e932ba..d5d7011 100644
--- a/test/testsupport/perf_test_result_writer.h
+++ b/test/testsupport/perf_test_result_writer.h
@@ -47,7 +47,7 @@
       const bool important,
       webrtc::test::ImproveDirection improve_direction) = 0;
 
-  virtual std::string ToJSON() const = 0;
+  virtual std::string Serialize() const = 0;
 };
 
 }  // namespace test
diff --git a/test/testsupport/perf_test_unittest.cc b/test/testsupport/perf_test_unittest.cc
index 8202471..1004c64 100644
--- a/test/testsupport/perf_test_unittest.cc
+++ b/test/testsupport/perf_test_unittest.cc
@@ -17,6 +17,11 @@
 #include "test/gtest.h"
 #include "test/testsupport/rtc_expect_death.h"
 
+#if WEBRTC_ENABLE_PROTOBUF
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
+namespace proto = catapult::tracing::tracing::proto;
+#endif
+
 namespace {
 
 const char* kJsonExpected = R"({
@@ -98,15 +103,72 @@
   const double kListOfScalars[] = {1, 2, 3};
   PrintResultList("foo", "bar", "baz_vl", kListOfScalars, "units", false);
 
-  EXPECT_EQ(RemoveSpaces(kJsonExpected), GetPerfResultsJSON());
+  EXPECT_EQ(RemoveSpaces(kJsonExpected), GetPerfResults());
 }
 
 TEST_F(PerfTest, TestClearPerfResults) {
   PrintResult("measurement", "modifier", "trace", 42, "units", false);
   ClearPerfResults();
-  EXPECT_EQ(R"({"format_version":"1.0","charts":{}})", GetPerfResultsJSON());
+  EXPECT_EQ(R"({"format_version":"1.0","charts":{}})", GetPerfResults());
 }
 
+#if WEBRTC_ENABLE_PROTOBUF
+
+TEST_F(PerfTest, TestGetPerfResultsHistograms) {
+  bool original_flag = absl::GetFlag(FLAGS_write_histogram_proto_json);
+  absl::SetFlag(&FLAGS_write_histogram_proto_json, true);
+  PrintResult("measurement", "_modifier", "story_1", 42, "ms", false);
+  PrintResult("foo", "bar", "story_1", 7, "sigma", true);
+  // Note: the error will be ignored, not supported by histograms.
+  PrintResultMeanAndError("foo", "bar", "story_1", 1, 2000, "sigma", false);
+  const double kListOfScalars[] = {1, 2, 3};
+  PrintResultList("foo", "bar", "story_1", kListOfScalars, "sigma", false);
+
+  proto::HistogramSet histogram_set;
+  EXPECT_TRUE(histogram_set.ParseFromString(GetPerfResults()))
+      << "Expected valid histogram set";
+
+  ASSERT_EQ(histogram_set.histograms_size(), 2)
+      << "Should be two histograms: foobar and measurement_modifier";
+  const proto::Histogram& hist1 = histogram_set.histograms(0);
+  const proto::Histogram& hist2 = histogram_set.histograms(1);
+
+  EXPECT_EQ(hist1.name(), "foobar");
+
+  // Spot check some things in here (there's a more thorough test on the
+  // histogram writer itself).
+  EXPECT_EQ(hist1.unit().unit(), proto::SIGMA);
+  EXPECT_EQ(hist1.sample_values_size(), 5);
+  EXPECT_EQ(hist1.sample_values(0), 7);
+  EXPECT_EQ(hist1.sample_values(1), 1);
+  EXPECT_EQ(hist1.sample_values(2), 1);
+  EXPECT_EQ(hist1.sample_values(3), 2);
+  EXPECT_EQ(hist1.sample_values(4), 3);
+
+  EXPECT_EQ(hist1.diagnostics().diagnostic_map().count("stories"), 1u);
+  const proto::Diagnostic& stories =
+      hist1.diagnostics().diagnostic_map().at("stories");
+  ASSERT_EQ(stories.generic_set().values_size(), 1);
+  EXPECT_EQ(stories.generic_set().values(0), "\"story_1\"");
+
+  EXPECT_EQ(hist2.name(), "measurement_modifier");
+  EXPECT_EQ(hist2.unit().unit(), proto::MS_BEST_FIT_FORMAT);
+
+  absl::SetFlag(&FLAGS_write_histogram_proto_json, original_flag);
+}
+
+TEST_F(PerfTest, TestClearPerfResultsHistograms) {
+  bool original_flag = absl::GetFlag(FLAGS_write_histogram_proto_json);
+  absl::SetFlag(&FLAGS_write_histogram_proto_json, true);
+  PrintResult("measurement", "modifier", "trace", 42, "ms", false);
+  ClearPerfResults();
+  EXPECT_EQ("", GetPerfResults());
+
+  absl::SetFlag(&FLAGS_write_histogram_proto_json, original_flag);
+}
+
+#endif  // WEBRTC_ENABLE_PROTOBUF
+
 #if GTEST_HAS_DEATH_TEST
 using PerfDeathTest = PerfTest;