Introduce PrintResultProxyMetricsExporter for migration from old to new API

Bug: b/246095034
Change-Id: I7597ddad84c4b2af1d59e38c558b1f0f56bd3f4e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/276047
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38141}
diff --git a/api/test/metrics/BUILD.gn b/api/test/metrics/BUILD.gn
index b2c0d7e..eb63ce0 100644
--- a/api/test/metrics/BUILD.gn
+++ b/api/test/metrics/BUILD.gn
@@ -26,6 +26,7 @@
 
     deps = [
       ":metrics_logger_and_exporter_test",
+      ":print_result_proxy_metrics_exporter_test",
       ":stdout_metrics_exporter_test",
     ]
 
@@ -143,6 +144,21 @@
   }
 }
 
+rtc_library("print_result_proxy_metrics_exporter") {
+  visibility = [ "*" ]
+  testonly = true
+  sources = [
+    "print_result_proxy_metrics_exporter.cc",
+    "print_result_proxy_metrics_exporter.h",
+  ]
+  deps = [
+    ":metric",
+    ":metrics_exporter",
+    "../..:array_view",
+    "../../../test:perf_test",
+  ]
+}
+
 if (rtc_include_tests) {
   rtc_library("stdout_metrics_exporter_test") {
     testonly = true
@@ -155,6 +171,17 @@
     ]
   }
 
+  rtc_library("print_result_proxy_metrics_exporter_test") {
+    testonly = true
+    sources = [ "print_result_proxy_metrics_exporter_test.cc" ]
+    deps = [
+      ":metric",
+      ":print_result_proxy_metrics_exporter",
+      "../../../test:test_support",
+      "../../units:timestamp",
+    ]
+  }
+
   rtc_library("metrics_logger_and_exporter_test") {
     testonly = true
     sources = [ "metrics_logger_and_exporter_test.cc" ]
diff --git a/api/test/metrics/print_result_proxy_metrics_exporter.cc b/api/test/metrics/print_result_proxy_metrics_exporter.cc
new file mode 100644
index 0000000..70cc9d5
--- /dev/null
+++ b/api/test/metrics/print_result_proxy_metrics_exporter.cc
@@ -0,0 +1,114 @@
+/*
+ *  Copyright (c) 2022 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 "api/test/metrics/print_result_proxy_metrics_exporter.h"
+
+#include <string>
+
+#include "api/array_view.h"
+#include "api/test/metrics/metric.h"
+#include "test/testsupport/perf_test.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+std::string ToPrintResultUnit(Unit unit) {
+  switch (unit) {
+    case Unit::kMilliseconds:
+      return "msBestFitFormat";
+    case Unit::kPercent:
+      return "n%";
+    case Unit::kBytes:
+      return "sizeInBytes";
+    case Unit::kKilobitsPerSecond:
+      // PrintResults prefer Chrome Perf Dashboard units, which doesn't have
+      // kpbs units, so we change the unit and value accordingly.
+      return "bytesPerSecond";
+    case Unit::kHertz:
+      return "Hz";
+    case Unit::kUnitless:
+      return "unitless";
+    case Unit::kCount:
+      return "count";
+  }
+}
+
+double ToPrintResultValue(double value, Unit unit) {
+  switch (unit) {
+    case Unit::kKilobitsPerSecond:
+      // PrintResults prefer Chrome Perf Dashboard units, which doesn't have
+      // kpbs units, so we change the unit and value accordingly.
+      return value * 1000 / 8;
+    default:
+      return value;
+  }
+}
+
+ImproveDirection ToPrintResultImproveDirection(ImprovementDirection direction) {
+  switch (direction) {
+    case ImprovementDirection::kBiggerIsBetter:
+      return ImproveDirection::kBiggerIsBetter;
+    case ImprovementDirection::kNeitherIsBetter:
+      return ImproveDirection::kNone;
+    case ImprovementDirection::kSmallerIsBetter:
+      return ImproveDirection::kSmallerIsBetter;
+  }
+}
+
+bool IsEmpty(const Metric::Stats& stats) {
+  return !stats.mean.has_value() && !stats.stddev.has_value() &&
+         !stats.min.has_value() && !stats.max.has_value();
+}
+
+}  // namespace
+
+bool PrintResultProxyMetricsExporter::Export(
+    rtc::ArrayView<const Metric> metrics) {
+  for (const Metric& metric : metrics) {
+    if (metric.time_series.samples.empty() && IsEmpty(metric.stats)) {
+      // If there were no data collected for the metric it is expected that 0
+      // will be exported, so add 0 to the samples.
+      PrintResult(metric.name, /*modifier=*/"", metric.test_case,
+                  ToPrintResultValue(0, metric.unit),
+                  ToPrintResultUnit(metric.unit), /*important=*/false,
+                  ToPrintResultImproveDirection(metric.improvement_direction));
+      continue;
+    }
+
+    if (metric.time_series.samples.empty()) {
+      PrintResultMeanAndError(
+          metric.name, /*modifier=*/"", metric.test_case,
+          ToPrintResultValue(*metric.stats.mean, metric.unit),
+          ToPrintResultValue(*metric.stats.stddev, metric.unit),
+          ToPrintResultUnit(metric.unit),
+          /*important=*/false,
+          ToPrintResultImproveDirection(metric.improvement_direction));
+      continue;
+    }
+
+    SamplesStatsCounter counter;
+    for (size_t i = 0; i < metric.time_series.samples.size(); ++i) {
+      counter.AddSample(SamplesStatsCounter::StatsSample{
+          .value = ToPrintResultValue(metric.time_series.samples[i].value,
+                                      metric.unit),
+          .time = metric.time_series.samples[i].timestamp,
+          .metadata = metric.time_series.samples[i].sample_metadata});
+    }
+
+    PrintResult(metric.name, /*modifier=*/"", metric.test_case, counter,
+                ToPrintResultUnit(metric.unit),
+                /*important=*/false,
+                ToPrintResultImproveDirection(metric.improvement_direction));
+  }
+  return true;
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/api/test/metrics/print_result_proxy_metrics_exporter.h b/api/test/metrics/print_result_proxy_metrics_exporter.h
new file mode 100644
index 0000000..bad0594
--- /dev/null
+++ b/api/test/metrics/print_result_proxy_metrics_exporter.h
@@ -0,0 +1,32 @@
+/*
+ *  Copyright (c) 2022 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.
+ */
+
+#ifndef API_TEST_METRICS_PRINT_RESULT_PROXY_METRICS_EXPORTER_H_
+#define API_TEST_METRICS_PRINT_RESULT_PROXY_METRICS_EXPORTER_H_
+
+#include "api/array_view.h"
+#include "api/test/metrics/metric.h"
+#include "api/test/metrics/metrics_exporter.h"
+
+namespace webrtc {
+namespace test {
+
+// Proxies all exported metrics to the `webrtc::test::PrintResult` API.
+class PrintResultProxyMetricsExporter : public MetricsExporter {
+ public:
+  ~PrintResultProxyMetricsExporter() override = default;
+
+  bool Export(rtc::ArrayView<const Metric> metrics) override;
+};
+
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // API_TEST_METRICS_PRINT_RESULT_PROXY_METRICS_EXPORTER_H_
diff --git a/api/test/metrics/print_result_proxy_metrics_exporter_test.cc b/api/test/metrics/print_result_proxy_metrics_exporter_test.cc
new file mode 100644
index 0000000..c783fba
--- /dev/null
+++ b/api/test/metrics/print_result_proxy_metrics_exporter_test.cc
@@ -0,0 +1,128 @@
+/*
+ *  Copyright (c) 2022 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 "api/test/metrics/print_result_proxy_metrics_exporter.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "api/test/metrics/metric.h"
+#include "api/units/timestamp.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+using ::testing::TestWithParam;
+
+std::map<std::string, std::string> DefaultMetadata() {
+  return std::map<std::string, std::string>{{"key", "value"}};
+}
+
+Metric::TimeSeries::Sample Sample(double value) {
+  return Metric::TimeSeries::Sample{.timestamp = Timestamp::Seconds(1),
+                                    .value = value,
+                                    .sample_metadata = DefaultMetadata()};
+}
+
+TEST(PrintResultProxyMetricsExporterTest,
+     ExportMetricsWithTimeSeriesFormatCorrect) {
+  Metric metric1{
+      .name = "test_metric1",
+      .unit = Unit::kMilliseconds,
+      .improvement_direction = ImprovementDirection::kBiggerIsBetter,
+      .test_case = "test_case_name1",
+      .metric_metadata = DefaultMetadata(),
+      .time_series =
+          Metric::TimeSeries{.samples = std::vector{Sample(10), Sample(20)}},
+      .stats =
+          Metric::Stats{.mean = 15.0, .stddev = 5.0, .min = 10.0, .max = 20.0}};
+  Metric metric2{
+      .name = "test_metric2",
+      .unit = Unit::kKilobitsPerSecond,
+      .improvement_direction = ImprovementDirection::kSmallerIsBetter,
+      .test_case = "test_case_name2",
+      .metric_metadata = DefaultMetadata(),
+      .time_series =
+          Metric::TimeSeries{.samples = std::vector{Sample(20), Sample(40)}},
+      .stats = Metric::Stats{
+          .mean = 30.0, .stddev = 10.0, .min = 20.0, .max = 40.0}};
+
+  testing::internal::CaptureStdout();
+  PrintResultProxyMetricsExporter exporter;
+
+  std::string expected =
+      "RESULT test_metric1: test_case_name1= {15,5} "
+      "msBestFitFormat_biggerIsBetter\n"
+      "RESULT test_metric2: test_case_name2= {3750,1250} "
+      "bytesPerSecond_smallerIsBetter\n";
+
+  EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric1, metric2}));
+  EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
+}
+
+TEST(PrintResultProxyMetricsExporterTest,
+     ExportMetricsWithStatsOnlyFormatCorrect) {
+  Metric metric1{.name = "test_metric1",
+                 .unit = Unit::kMilliseconds,
+                 .improvement_direction = ImprovementDirection::kBiggerIsBetter,
+                 .test_case = "test_case_name1",
+                 .metric_metadata = DefaultMetadata(),
+                 .time_series = Metric::TimeSeries{.samples = {}},
+                 .stats = Metric::Stats{
+                     .mean = 15.0, .stddev = 5.0, .min = 10.0, .max = 20.0}};
+  Metric metric2{
+      .name = "test_metric2",
+      .unit = Unit::kKilobitsPerSecond,
+      .improvement_direction = ImprovementDirection::kSmallerIsBetter,
+      .test_case = "test_case_name2",
+      .metric_metadata = DefaultMetadata(),
+      .time_series = Metric::TimeSeries{.samples = {}},
+      .stats = Metric::Stats{
+          .mean = 30.0, .stddev = 10.0, .min = 20.0, .max = 40.0}};
+
+  testing::internal::CaptureStdout();
+  PrintResultProxyMetricsExporter exporter;
+
+  std::string expected =
+      "RESULT test_metric1: test_case_name1= {15,5} "
+      "msBestFitFormat_biggerIsBetter\n"
+      "RESULT test_metric2: test_case_name2= {3750,1250} "
+      "bytesPerSecond_smallerIsBetter\n";
+
+  EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric1, metric2}));
+  EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
+}
+
+TEST(PrintResultProxyMetricsExporterTest, ExportEmptyMetricOnlyFormatCorrect) {
+  Metric metric{.name = "test_metric",
+                .unit = Unit::kMilliseconds,
+                .improvement_direction = ImprovementDirection::kBiggerIsBetter,
+                .test_case = "test_case_name",
+                .metric_metadata = DefaultMetadata(),
+                .time_series = Metric::TimeSeries{.samples = {}},
+                .stats = Metric::Stats{}};
+
+  testing::internal::CaptureStdout();
+  PrintResultProxyMetricsExporter exporter;
+
+  std::string expected =
+      "RESULT test_metric: test_case_name= 0 "
+      "msBestFitFormat_biggerIsBetter\n";
+
+  EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
+  EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace webrtc