Introduce MetricsExporter API with stdout implementation

Bug: b/246095034
Change-Id: I9979fb03b9a02e76808145f43910420524fe633a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/274880
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38107}
This commit is contained in:
Artem Titov 2022-09-17 00:12:19 +02:00 committed by WebRTC LUCI CQ
parent acd375723b
commit 27f91afa38
6 changed files with 430 additions and 3 deletions

View File

@ -52,7 +52,6 @@ if (!build_with_chromium) {
":voip_unittests", ":voip_unittests",
":webrtc_nonparallel_tests", ":webrtc_nonparallel_tests",
":webrtc_perf_tests", ":webrtc_perf_tests",
"api/test/metrics:metrics_unittests",
"common_audio:common_audio_unittests", "common_audio:common_audio_unittests",
"common_video:common_video_unittests", "common_video:common_video_unittests",
"examples:examples_unittests", "examples:examples_unittests",
@ -565,6 +564,7 @@ if (rtc_include_tests && !build_with_chromium) {
"api/audio_codecs/test:audio_codecs_api_unittests", "api/audio_codecs/test:audio_codecs_api_unittests",
"api/numerics:numerics_unittests", "api/numerics:numerics_unittests",
"api/task_queue:pending_task_safety_flag_unittests", "api/task_queue:pending_task_safety_flag_unittests",
"api/test/metrics:metrics_unittests",
"api/transport:stun_unittest", "api/transport:stun_unittest",
"api/video/test:rtc_api_video_unittests", "api/video/test:rtc_api_video_unittests",
"api/video_codecs/test:video_codecs_api_unittests", "api/video_codecs/test:video_codecs_api_unittests",

View File

@ -9,14 +9,18 @@
import("../../../webrtc.gni") import("../../../webrtc.gni")
group("metrics") { group("metrics") {
deps = [ ":metric" ] deps = [
":metric",
":metrics_exporter",
":stdout_metrics_exporter",
]
} }
if (rtc_include_tests) { if (rtc_include_tests) {
group("metrics_unittests") { group("metrics_unittests") {
testonly = true testonly = true
deps = [] deps = [ ":stdout_metrics_exporter_test" ]
} }
} }
@ -29,3 +33,40 @@ rtc_library("metric") {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
deps = [ "../../../api/units:timestamp" ] deps = [ "../../../api/units:timestamp" ]
} }
rtc_library("metrics_exporter") {
visibility = [ "*" ]
sources = [ "metrics_exporter.h" ]
deps = [
":metric",
"../../../api:array_view",
]
}
rtc_library("stdout_metrics_exporter") {
visibility = [ "*" ]
sources = [
"stdout_metrics_exporter.cc",
"stdout_metrics_exporter.h",
]
deps = [
":metric",
":metrics_exporter",
"../../../api:array_view",
"../../../rtc_base:stringutils",
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
if (rtc_include_tests) {
rtc_library("stdout_metrics_exporter_test") {
testonly = true
sources = [ "stdout_metrics_exporter_test.cc" ]
deps = [
":metric",
":stdout_metrics_exporter",
"../../../api/units:timestamp",
"../../../test:test_support",
]
}
}

View File

@ -0,0 +1,33 @@
/*
* 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_METRICS_EXPORTER_H_
#define API_TEST_METRICS_METRICS_EXPORTER_H_
#include "api/array_view.h"
#include "api/test/metrics/metric.h"
namespace webrtc {
namespace test {
// Exports metrics in the requested format.
class MetricsExporter {
public:
virtual ~MetricsExporter() = default;
// Exports specified metrics in a format that depends on the implementation.
// Returns true if export succeeded, false otherwise.
virtual bool Export(rtc::ArrayView<const Metric> metrics) = 0;
};
} // namespace test
} // namespace webrtc
#endif // API_TEST_METRICS_METRICS_EXPORTER_H_

View File

@ -0,0 +1,101 @@
/*
* 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/stdout_metrics_exporter.h"
#include <stdio.h>
#include <cmath>
#include <string>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/test/metrics/metric.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
namespace test {
namespace {
// Returns positive integral part of the number.
int64_t IntegralPart(double value) {
return std::lround(std::floor(std::abs(value)));
}
void AppendWithPrecision(double value,
int digits_after_comma,
rtc::StringBuilder& out) {
int64_t multiplier = std::lround(std::pow(10, digits_after_comma));
int64_t integral_part = IntegralPart(value);
double decimal_part = std::abs(value) - integral_part;
// If decimal part has leading zeros then when it will be multiplied on
// `multiplier`, leading zeros will be lost. To preserve them we add "1"
// so then leading digit will be greater than 0 and won't be removed.
//
// During conversion to the string leading digit has to be stripped.
//
// Also due to rounding it may happen that leading digit may be incremented,
// like with `digits_after_comma` 3 number 1.9995 will be rounded to 2. In
// such case this increment has to be propagated to the `integral_part`.
int64_t decimal_holder = std::lround((1 + decimal_part) * multiplier);
if (decimal_holder >= 2 * multiplier) {
// Rounding incremented added leading digit, so we need to transfer 1 to
// integral part.
integral_part++;
decimal_holder -= multiplier;
}
// Remove trailing zeros.
while (decimal_holder % 10 == 0) {
decimal_holder /= 10;
}
// Print serialized number to output.
if (value < 0) {
out << "-";
}
out << integral_part;
if (decimal_holder != 1) {
out << "." << std::to_string(decimal_holder).substr(1, digits_after_comma);
}
}
} // namespace
StdoutMetricsExporter::StdoutMetricsExporter() : output_(stdout) {}
bool StdoutMetricsExporter::Export(rtc::ArrayView<const Metric> metrics) {
for (const Metric& metric : metrics) {
PrintMetric(metric);
}
return true;
}
void StdoutMetricsExporter::PrintMetric(const Metric& metric) {
rtc::StringBuilder value_stream;
value_stream << metric.test_case << "/" << metric.name << "= {mean=";
if (metric.stats.mean.has_value()) {
AppendWithPrecision(*metric.stats.mean, 8, value_stream);
} else {
value_stream << "-";
}
value_stream << ", stddev=";
if (metric.stats.stddev.has_value()) {
AppendWithPrecision(*metric.stats.stddev, 8, value_stream);
} else {
value_stream << "-";
}
value_stream << "} " << ToString(metric.unit) << " ("
<< ToString(metric.improvement_direction) << ")";
fprintf(output_, "RESULT: %s\n", value_stream.str().c_str());
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,41 @@
/*
* 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_STDOUT_METRICS_EXPORTER_H_
#define API_TEST_METRICS_STDOUT_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 {
// Exports all collected metrics to stdout.
class StdoutMetricsExporter : public MetricsExporter {
public:
StdoutMetricsExporter();
~StdoutMetricsExporter() override = default;
StdoutMetricsExporter(const StdoutMetricsExporter&) = delete;
StdoutMetricsExporter& operator=(const StdoutMetricsExporter&) = delete;
bool Export(rtc::ArrayView<const Metric> metrics) override;
private:
void PrintMetric(const Metric& metric);
FILE* const output_;
};
} // namespace test
} // namespace webrtc
#endif // API_TEST_METRICS_STDOUT_METRICS_EXPORTER_H_

View File

@ -0,0 +1,211 @@
/*
* 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/stdout_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()};
}
Metric PsnrForTestFoo(double mean, double stddev) {
return Metric{.name = "psnr",
.unit = Unit::kUnitless,
.improvement_direction = ImprovementDirection::kBiggerIsBetter,
.test_case = "foo",
.time_series = Metric::TimeSeries{},
.stats = Metric::Stats{.mean = mean, .stddev = stddev}};
}
TEST(StdoutMetricsExporterTest, MAYBE_ExportMetricFormatCorrect) {
Metric metric1{
.name = "test_metric1",
.unit = Unit::kTimeMs,
.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();
StdoutMetricsExporter exporter;
std::string expected =
"RESULT: test_case_name1/test_metric1= "
"{mean=15, stddev=5} TimeMs (BiggerIsBetter)\n"
"RESULT: test_case_name2/test_metric2= "
"{mean=30, stddev=10} KilobitsPerSecond (SmallerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric1, metric2}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest, PositiveNumberMaxPrecision) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(15.00000001, 0.00000001);
std::string expected =
"RESULT: foo/psnr= "
"{mean=15.00000001, stddev=0.00000001} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
PositiveNumberTrailingZeroNotAdded) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(15.12345, 0.12);
std::string expected =
"RESULT: foo/psnr= "
"{mean=15.12345, stddev=0.12} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
PositiveNumberTrailingZeroAreRemoved) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(15.123450000, 0.120000000);
std::string expected =
"RESULT: foo/psnr= "
"{mean=15.12345, stddev=0.12} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
PositiveNumberRoundsUpOnPrecisionCorrectly) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(15.000000009, 0.999999999);
std::string expected =
"RESULT: foo/psnr= "
"{mean=15.00000001, stddev=1} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
PositiveNumberRoundsDownOnPrecisionCorrectly) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(15.0000000049, 0.9999999949);
std::string expected =
"RESULT: foo/psnr= "
"{mean=15, stddev=0.99999999} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest, NegativeNumberMaxPrecision) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(-15.00000001, -0.00000001);
std::string expected =
"RESULT: foo/psnr= "
"{mean=-15.00000001, stddev=-0.00000001} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
NegativeNumberTrailingZeroNotAdded) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(-15.12345, -0.12);
std::string expected =
"RESULT: foo/psnr= "
"{mean=-15.12345, stddev=-0.12} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
NegativeNumberTrailingZeroAreRemoved) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(-15.123450000, -0.120000000);
std::string expected =
"RESULT: foo/psnr= "
"{mean=-15.12345, stddev=-0.12} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
NegativeNumberRoundsUpOnPrecisionCorrectly) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(-15.000000009, -0.999999999);
std::string expected =
"RESULT: foo/psnr= "
"{mean=-15.00000001, stddev=-1} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
TEST(StdoutMetricsExporterNumberFormatTest,
NegativeNumberRoundsDownOnPrecisionCorrectly) {
testing::internal::CaptureStdout();
StdoutMetricsExporter exporter;
Metric metric = PsnrForTestFoo(-15.0000000049, -0.9999999949);
std::string expected =
"RESULT: foo/psnr= "
"{mean=-15, stddev=-0.99999999} Unitless (BiggerIsBetter)\n";
EXPECT_TRUE(exporter.Export(std::vector<Metric>{metric}));
EXPECT_EQ(expected, testing::internal::GetCapturedStdout());
}
} // namespace
} // namespace test
} // namespace webrtc